From 64c52db78a3c9d4e7a6e2f9a73735c46cd46d069 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 30 May 2018 17:58:44 -0500 Subject: [PATCH 0001/1582] upstream commit --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a14625c19..226f4dfd6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.1+git' # check versioning +version: '5.4.4.2+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 0ebe32190cea0b8b9083782b0c03fb35a5e864b7 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 27 Jun 2018 18:08:20 -0500 Subject: [PATCH 0002/1582] updated assoc packages --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 226f4dfd6..b756f344f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.2+git' # check versioning +version: '5.4.4.3+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b9a0303e28286b8e4b27e41d70c75b3be49da7c1 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Fri, 29 Jun 2018 18:59:21 -0500 Subject: [PATCH 0003/1582] minor update https://github.com/softlayer/softlayer-python/commit/474e3386f9e7ecd2a22d47d04badf9648f617c39 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b756f344f..42e328b0b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.3+git' # check versioning +version: '5.4.4.4+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 3d11f842536bfa8e025c784e8527a24927eedce3 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 5 Jul 2018 17:00:56 -0500 Subject: [PATCH 0004/1582] patch for slcli shell --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 42e328b0b..64a38cea2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.4+git' # check versioning +version: '5.4.4.5+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b184c840c83a7ffa0f1c6f67b9a67ebc2e1cc1ec Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:08:46 -0500 Subject: [PATCH 0005/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 64a38cea2..4cf19b464 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.5+git' # check versioning +version: '5.4.4.6+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 474265d80e5ee99115f6956e2d4b1371750a3ff9 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:51:35 -0500 Subject: [PATCH 0006/1582] fixed versioning 5.5.1 --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 4cf19b464..c05b79e30 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.4.4.6+git' # check versioning +version: '5.5.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 2ba0561a59bd1cb05bd552594290457f0cd80425 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 10 Jul 2018 20:51:57 -0500 Subject: [PATCH 0007/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c05b79e30..9ede1600d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.1+git' # check versioning +version: '5.5.0.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From ad1661b51cf74bc5d524e19f720a68bcb6239a94 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Sun, 22 Jul 2018 20:06:52 -0500 Subject: [PATCH 0008/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9ede1600d..938101da5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.1+git' # check versioning +version: '5.5.0.2+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 3cfa16dbf4e20c9d6df955aae89975f4296600e1 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Fri, 27 Jul 2018 16:30:34 -0500 Subject: [PATCH 0009/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 938101da5..bc5dd3199 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.2+git' # check versioning +version: '5.5.0.3+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 99c2126e658ed2b0f6e21f0fd59c59c5be1ab8c6 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 2 Aug 2018 17:06:37 -0500 Subject: [PATCH 0010/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bc5dd3199..81870776b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.3+git' # check versioning +version: '5.5.0.4+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 9445a89837a0df7cf0d0cd54c658d5976213da08 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 8 Aug 2018 22:44:03 -0500 Subject: [PATCH 0011/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 81870776b..c05b79e30 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.5.0.4+git' # check versioning +version: '5.5.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 563f5da1ea93195ba24d2c3dd45710c17b44ece8 Mon Sep 17 00:00:00 2001 From: Erick Sapp Date: Sun, 10 Feb 2019 16:05:39 -0600 Subject: [PATCH 0012/1582] Added limit option to 'event-log get'. --- SoftLayer/CLI/event_log/get.py | 19 +++++++++++++++---- SoftLayer/managers/event_log.py | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/event_log/get.py b/SoftLayer/CLI/event_log/get.py index b505a4502..6e07d7645 100644 --- a/SoftLayer/CLI/event_log/get.py +++ b/SoftLayer/CLI/event_log/get.py @@ -27,13 +27,16 @@ help="UTC Offset for searching with dates. The default is -0000") @click.option('--metadata/--no-metadata', default=False, help="Display metadata if present") +@click.option('--limit', '-l', default=30, + help="How many results to get in one api call, default is 30.") @environment.pass_env -def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metadata): +def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metadata, limit): """Get Event Logs""" + mgr = SoftLayer.EventLogManager(env.client) usrmgr = SoftLayer.UserManager(env.client) request_filter = mgr.build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset) - logs = mgr.get_event_logs(request_filter) + logs = mgr.get_event_logs(request_filter, log_limit=limit) if logs is None: env.fout('None available.') @@ -43,11 +46,19 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada COLUMNS.append('metadata') table = formatting.Table(COLUMNS) + if metadata: table.align['metadata'] = "l" for log in logs: user = log['userType'] + label = '' + + try: + label = log['label'] + except KeyError: + pass # label is already at default value. + if user == "CUSTOMER": user = usrmgr.get_user(log['userId'], "mask[username]")['username'] if metadata: @@ -58,9 +69,9 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada except ValueError: metadata_data = log['metaData'] - table.add_row([log['eventName'], log['label'], log['objectName'], + table.add_row([log['eventName'], label, log['objectName'], log['eventCreateDate'], user, metadata_data]) else: - table.add_row([log['eventName'], log['label'], log['objectName'], + table.add_row([log['eventName'], label, log['objectName'], log['eventCreateDate'], user]) env.fout(table) diff --git a/SoftLayer/managers/event_log.py b/SoftLayer/managers/event_log.py index 4e37e6d67..9e36471c3 100644 --- a/SoftLayer/managers/event_log.py +++ b/SoftLayer/managers/event_log.py @@ -19,13 +19,13 @@ class EventLogManager(object): def __init__(self, client): self.event_log = client['Event_Log'] - def get_event_logs(self, request_filter): + def get_event_logs(self, request_filter, log_limit=10): """Returns a list of event logs :param dict request_filter: filter dict :returns: List of event logs """ - results = self.event_log.getAllObjects(filter=request_filter) + results = self.event_log.getAllObjects(filter=request_filter, limit=log_limit) return results def get_event_log_types(self): From e674e13e9f9bfee31ac6ff9d99d5ad4dc7ac16ca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 21 Feb 2019 14:35:16 -0600 Subject: [PATCH 0013/1582] #1089 removed legacy SL message queue commands --- SoftLayer/CLI/mq/__init__.py | 57 ---- SoftLayer/CLI/mq/accounts_list.py | 28 -- SoftLayer/CLI/mq/endpoints_list.py | 27 -- SoftLayer/CLI/mq/ping.py | 25 -- SoftLayer/CLI/mq/queue_add.py | 46 --- SoftLayer/CLI/mq/queue_detail.py | 26 -- SoftLayer/CLI/mq/queue_edit.py | 46 --- SoftLayer/CLI/mq/queue_list.py | 34 -- SoftLayer/CLI/mq/queue_pop.py | 42 --- SoftLayer/CLI/mq/queue_push.py | 32 -- SoftLayer/CLI/mq/queue_remove.py | 30 -- SoftLayer/CLI/mq/topic_add.py | 46 --- SoftLayer/CLI/mq/topic_detail.py | 30 -- SoftLayer/CLI/mq/topic_list.py | 29 -- SoftLayer/CLI/mq/topic_push.py | 34 -- SoftLayer/CLI/mq/topic_remove.py | 25 -- SoftLayer/CLI/mq/topic_subscribe.py | 46 --- SoftLayer/CLI/mq/topic_unsubscribe.py | 25 -- SoftLayer/CLI/routes.py | 19 -- SoftLayer/managers/__init__.py | 2 - SoftLayer/managers/messaging.py | 405 ---------------------- tests/managers/queue_tests.py | 467 -------------------------- 22 files changed, 1521 deletions(-) delete mode 100644 SoftLayer/CLI/mq/__init__.py delete mode 100644 SoftLayer/CLI/mq/accounts_list.py delete mode 100644 SoftLayer/CLI/mq/endpoints_list.py delete mode 100644 SoftLayer/CLI/mq/ping.py delete mode 100644 SoftLayer/CLI/mq/queue_add.py delete mode 100644 SoftLayer/CLI/mq/queue_detail.py delete mode 100644 SoftLayer/CLI/mq/queue_edit.py delete mode 100644 SoftLayer/CLI/mq/queue_list.py delete mode 100644 SoftLayer/CLI/mq/queue_pop.py delete mode 100644 SoftLayer/CLI/mq/queue_push.py delete mode 100644 SoftLayer/CLI/mq/queue_remove.py delete mode 100644 SoftLayer/CLI/mq/topic_add.py delete mode 100644 SoftLayer/CLI/mq/topic_detail.py delete mode 100644 SoftLayer/CLI/mq/topic_list.py delete mode 100644 SoftLayer/CLI/mq/topic_push.py delete mode 100644 SoftLayer/CLI/mq/topic_remove.py delete mode 100644 SoftLayer/CLI/mq/topic_subscribe.py delete mode 100644 SoftLayer/CLI/mq/topic_unsubscribe.py delete mode 100644 SoftLayer/managers/messaging.py delete mode 100644 tests/managers/queue_tests.py diff --git a/SoftLayer/CLI/mq/__init__.py b/SoftLayer/CLI/mq/__init__.py deleted file mode 100644 index 3f8947b89..000000000 --- a/SoftLayer/CLI/mq/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Message queue service.""" -# :license: MIT, see LICENSE for more details. - -from SoftLayer.CLI import formatting - - -def queue_table(queue): - """Returns a table with details about a queue.""" - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', queue['name']]) - table.add_row(['message_count', queue['message_count']]) - table.add_row(['visible_message_count', queue['visible_message_count']]) - table.add_row(['tags', formatting.listing(queue['tags'] or [])]) - table.add_row(['expiration', queue['expiration']]) - table.add_row(['visibility_interval', queue['visibility_interval']]) - return table - - -def message_table(message): - """Returns a table with details about a message.""" - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', message['id']]) - table.add_row(['initial_entry_time', message['initial_entry_time']]) - table.add_row(['visibility_delay', message['visibility_delay']]) - table.add_row(['visibility_interval', message['visibility_interval']]) - table.add_row(['fields', message['fields']]) - return [table, message['body']] - - -def topic_table(topic): - """Returns a table with details about a topic.""" - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['name', topic['name']]) - table.add_row(['tags', formatting.listing(topic['tags'] or [])]) - return table - - -def subscription_table(sub): - """Returns a table with details about a subscription.""" - table = formatting.Table(['property', 'value']) - table.align['property'] = 'r' - table.align['value'] = 'l' - - table.add_row(['id', sub['id']]) - table.add_row(['endpoint_type', sub['endpoint_type']]) - for key, val in sub['endpoint'].items(): - table.add_row([key, val]) - return table diff --git a/SoftLayer/CLI/mq/accounts_list.py b/SoftLayer/CLI/mq/accounts_list.py deleted file mode 100644 index 484881b5b..000000000 --- a/SoftLayer/CLI/mq/accounts_list.py +++ /dev/null @@ -1,28 +0,0 @@ -"""List SoftLayer Message Queue Accounts.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """List SoftLayer Message Queue Accounts.""" - - manager = SoftLayer.MessagingManager(env.client) - accounts = manager.list_accounts() - - table = formatting.Table(['id', 'name', 'status']) - for account in accounts: - if not account['nodes']: - continue - - table.add_row([account['nodes'][0]['accountName'], - account['name'], - account['status']['name']]) - - env.fout(table) diff --git a/SoftLayer/CLI/mq/endpoints_list.py b/SoftLayer/CLI/mq/endpoints_list.py deleted file mode 100644 index 556642ccd..000000000 --- a/SoftLayer/CLI/mq/endpoints_list.py +++ /dev/null @@ -1,27 +0,0 @@ -"""List SoftLayer Message Queue Endpoints.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """List SoftLayer Message Queue Endpoints.""" - - manager = SoftLayer.MessagingManager(env.client) - regions = manager.get_endpoints() - - table = formatting.Table(['name', 'public', 'private']) - for region, endpoints in regions.items(): - table.add_row([ - region, - endpoints.get('public') or formatting.blank(), - endpoints.get('private') or formatting.blank(), - ]) - - env.fout(table) diff --git a/SoftLayer/CLI/mq/ping.py b/SoftLayer/CLI/mq/ping.py deleted file mode 100644 index 2a8fa2435..000000000 --- a/SoftLayer/CLI/mq/ping.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Ping the SoftLayer Message Queue service.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions - - -@click.command() -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, datacenter, network): - """Ping the SoftLayer Message Queue service.""" - - manager = SoftLayer.MessagingManager(env.client) - okay = manager.ping(datacenter=datacenter, network=network) - if okay: - env.fout('OK') - else: - exceptions.CLIAbort('Ping failed') diff --git a/SoftLayer/CLI/mq/queue_add.py b/SoftLayer/CLI/mq/queue_add.py deleted file mode 100644 index 594527116..000000000 --- a/SoftLayer/CLI/mq/queue_add.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Create a queue.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@click.option('--visibility-interval', - type=click.INT, - default=30, - show_default=True, - help="Time in seconds that messages will re-appear after being " - "popped") -@click.option('--expiration', - type=click.INT, - default=604800, - show_default=True, - help="Time in seconds that messages will live") -@helpers.multi_option('--tag', '-g', help="Tags to add to the queue") -@environment.pass_env -def cli(env, account_id, queue_name, datacenter, network, visibility_interval, - expiration, tag): - """Create a queue.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - queue = mq_client.create_queue( - queue_name, - visibility_interval=visibility_interval, - expiration=expiration, - tags=tag, - ) - env.fout(mq.queue_table(queue)) diff --git a/SoftLayer/CLI/mq/queue_detail.py b/SoftLayer/CLI/mq/queue_detail.py deleted file mode 100644 index 3cd1694d5..000000000 --- a/SoftLayer/CLI/mq/queue_detail.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Detail a queue.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, queue_name, datacenter, network): - """Detail a queue.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - queue = mq_client.get_queue(queue_name) - env.fout(mq.queue_table(queue)) diff --git a/SoftLayer/CLI/mq/queue_edit.py b/SoftLayer/CLI/mq/queue_edit.py deleted file mode 100644 index 1f97788bf..000000000 --- a/SoftLayer/CLI/mq/queue_edit.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Modify a queue.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@click.option('--visibility-interval', - type=click.INT, - default=30, - show_default=True, - help="Time in seconds that messages will re-appear after being " - "popped") -@click.option('--expiration', - type=click.INT, - default=604800, - show_default=True, - help="Time in seconds that messages will live") -@helpers.multi_option('--tag', '-g', help="Tags to add to the queue") -@environment.pass_env -def cli(env, account_id, queue_name, datacenter, network, visibility_interval, - expiration, tag): - """Modify a queue.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - queue = mq_client.modify_queue( - queue_name, - visibility_interval=visibility_interval, - expiration=expiration, - tags=tag, - ) - env.fout(mq.queue_table(queue)) diff --git a/SoftLayer/CLI/mq/queue_list.py b/SoftLayer/CLI/mq/queue_list.py deleted file mode 100644 index 1059fa3c9..000000000 --- a/SoftLayer/CLI/mq/queue_list.py +++ /dev/null @@ -1,34 +0,0 @@ -"""List all queues on an account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@click.argument('account-id') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, datacenter, network): - """List all queues on an account.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - queues = mq_client.get_queues()['items'] - - table = formatting.Table(['name', - 'message_count', - 'visible_message_count']) - for queue in queues: - table.add_row([queue['name'], - queue['message_count'], - queue['visible_message_count']]) - env.fout(table) diff --git a/SoftLayer/CLI/mq/queue_pop.py b/SoftLayer/CLI/mq/queue_pop.py deleted file mode 100644 index 1d81e9ea9..000000000 --- a/SoftLayer/CLI/mq/queue_pop.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Pops a message from a queue.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.option('--count', - default=1, - show_default=True, - type=click.INT, - help="Count of messages to pop") -@click.option('--delete-after', - is_flag=True, - help="Remove popped messages from the queue") -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, queue_name, count, delete_after, datacenter, network): - """Pops a message from a queue.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - messages = mq_client.pop_messages(queue_name, count) - formatted_messages = [] - for message in messages['items']: - formatted_messages.append(mq.message_table(message)) - - if delete_after: - for message in messages['items']: - mq_client.delete_message(queue_name, message['id']) - env.fout(formatted_messages) diff --git a/SoftLayer/CLI/mq/queue_push.py b/SoftLayer/CLI/mq/queue_push.py deleted file mode 100644 index c025dcf94..000000000 --- a/SoftLayer/CLI/mq/queue_push.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Push a message into a queue.""" -# :license: MIT, see LICENSE for more details. -import sys - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.argument('message') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, queue_name, message, datacenter, network): - """Push a message into a queue.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - body = '' - if message == '-': - body = sys.stdin.read() - else: - body = message - env.fout(mq.message_table(mq_client.push_queue_message(queue_name, body))) diff --git a/SoftLayer/CLI/mq/queue_remove.py b/SoftLayer/CLI/mq/queue_remove.py deleted file mode 100644 index 4396dac1a..000000000 --- a/SoftLayer/CLI/mq/queue_remove.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Delete a queue or a queued message.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment - - -@click.command() -@click.argument('account-id') -@click.argument('queue-name') -@click.argument('message-id', required=False) -@click.option('--force', is_flag=True, help="Force the deletion of the queue") -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, queue_name, message_id, force, datacenter, network): - """Delete a queue or a queued message.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - if message_id: - mq_client.delete_message(queue_name, message_id) - else: - mq_client.delete_queue(queue_name, force) diff --git a/SoftLayer/CLI/mq/topic_add.py b/SoftLayer/CLI/mq/topic_add.py deleted file mode 100644 index 0b48874c2..000000000 --- a/SoftLayer/CLI/mq/topic_add.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Create a new topic.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import helpers -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@click.option('--visibility-interval', - type=click.INT, - default=30, - show_default=True, - help="Time in seconds that messages will re-appear after being " - "popped") -@click.option('--expiration', - type=click.INT, - default=604800, - show_default=True, - help="Time in seconds that messages will live") -@helpers.multi_option('--tag', '-g', help="Tags to add to the topic") -@environment.pass_env -def cli(env, account_id, topic_name, datacenter, network, - visibility_interval, expiration, tag): - """Create a new topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - topic = mq_client.create_topic( - topic_name, - visibility_interval=visibility_interval, - expiration=expiration, - tags=tag, - ) - env.fout(mq.topic_table(topic)) diff --git a/SoftLayer/CLI/mq/topic_detail.py b/SoftLayer/CLI/mq/topic_detail.py deleted file mode 100644 index bb0b80349..000000000 --- a/SoftLayer/CLI/mq/topic_detail.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Detail a topic.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, topic_name, datacenter, network): - """Detail a topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - topic = mq_client.get_topic(topic_name) - subscriptions = mq_client.get_subscriptions(topic_name) - tables = [] - for sub in subscriptions['items']: - tables.append(mq.subscription_table(sub)) - env.fout([mq.topic_table(topic), tables]) diff --git a/SoftLayer/CLI/mq/topic_list.py b/SoftLayer/CLI/mq/topic_list.py deleted file mode 100644 index f86d13334..000000000 --- a/SoftLayer/CLI/mq/topic_list.py +++ /dev/null @@ -1,29 +0,0 @@ -"""List all topics on an account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@click.argument('account-id') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, datacenter, network): - """List all topics on an account.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - topics = mq_client.get_topics()['items'] - - table = formatting.Table(['name']) - for topic in topics: - table.add_row([topic['name']]) - env.fout(table) diff --git a/SoftLayer/CLI/mq/topic_push.py b/SoftLayer/CLI/mq/topic_push.py deleted file mode 100644 index e0384492f..000000000 --- a/SoftLayer/CLI/mq/topic_push.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Push a message into a topic.""" -# :license: MIT, see LICENSE for more details. -import sys - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.argument('message') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, topic_name, message, datacenter, network): - """Push a message into a topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - - # the message body comes from the positional argument or stdin - body = '' - if message == '-': - body = sys.stdin.read() - else: - body = message - env.fout(mq.message_table(mq_client.push_topic_message(topic_name, body))) diff --git a/SoftLayer/CLI/mq/topic_remove.py b/SoftLayer/CLI/mq/topic_remove.py deleted file mode 100644 index cdfb22c0f..000000000 --- a/SoftLayer/CLI/mq/topic_remove.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Delete a topic.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.option('--force', is_flag=True, help="Force the deletion of the queue") -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, topic_name, force, datacenter, network): - """Delete a topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - mq_client.delete_topic(topic_name, force) diff --git a/SoftLayer/CLI/mq/topic_subscribe.py b/SoftLayer/CLI/mq/topic_subscribe.py deleted file mode 100644 index 226e20103..000000000 --- a/SoftLayer/CLI/mq/topic_subscribe.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Create a subscription on a topic.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import mq - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@click.option('--sub-type', - type=click.Choice(['http', 'queue']), - help="Type of endpoint") -@click.option('--queue-name', help="Queue name. Required if --type is queue") -@click.option('--http-method', help="HTTP Method to use if --type is http") -@click.option('--http-url', - help="HTTP/HTTPS URL to use. Required if --type is http") -@click.option('--http-body', - help="HTTP Body template to use if --type is http") -@environment.pass_env -def cli(env, account_id, topic_name, datacenter, network, sub_type, queue_name, - http_method, http_url, http_body): - """Create a subscription on a topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - if sub_type == 'queue': - subscription = mq_client.create_subscription(topic_name, 'queue', - queue_name=queue_name) - elif sub_type == 'http': - subscription = mq_client.create_subscription( - topic_name, - 'http', - method=http_method, - url=http_url, - body=http_body, - ) - env.fout(mq.subscription_table(subscription)) diff --git a/SoftLayer/CLI/mq/topic_unsubscribe.py b/SoftLayer/CLI/mq/topic_unsubscribe.py deleted file mode 100644 index 48d4b4940..000000000 --- a/SoftLayer/CLI/mq/topic_unsubscribe.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Remove a subscription on a topic.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment - - -@click.command() -@click.argument('account-id') -@click.argument('topic-name') -@click.argument('subscription-id') -@click.option('--datacenter', help="Datacenter, E.G.: dal05") -@click.option('--network', - type=click.Choice(['public', 'private']), - help="Network type") -@environment.pass_env -def cli(env, account_id, topic_name, subscription_id, datacenter, network): - """Remove a subscription on a topic.""" - - manager = SoftLayer.MessagingManager(env.client) - mq_client = manager.get_connection(account_id, - datacenter=datacenter, network=network) - mq_client.delete_subscription(topic_name, subscription_id) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc6a86abe..e5b28c0e3 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -181,25 +181,6 @@ ('loadbal:service-edit', 'SoftLayer.CLI.loadbal.service_edit:cli'), ('loadbal:service-toggle', 'SoftLayer.CLI.loadbal.service_toggle:cli'), - ('messaging', 'SoftLayer.CLI.mq'), - ('messaging:accounts-list', 'SoftLayer.CLI.mq.accounts_list:cli'), - ('messaging:endpoints-list', 'SoftLayer.CLI.mq.endpoints_list:cli'), - ('messaging:ping', 'SoftLayer.CLI.mq.ping:cli'), - ('messaging:queue-add', 'SoftLayer.CLI.mq.queue_add:cli'), - ('messaging:queue-detail', 'SoftLayer.CLI.mq.queue_detail:cli'), - ('messaging:queue-edit', 'SoftLayer.CLI.mq.queue_edit:cli'), - ('messaging:queue-list', 'SoftLayer.CLI.mq.queue_list:cli'), - ('messaging:queue-pop', 'SoftLayer.CLI.mq.queue_pop:cli'), - ('messaging:queue-push', 'SoftLayer.CLI.mq.queue_push:cli'), - ('messaging:queue-remove', 'SoftLayer.CLI.mq.queue_remove:cli'), - ('messaging:topic-add', 'SoftLayer.CLI.mq.topic_add:cli'), - ('messaging:topic-detail', 'SoftLayer.CLI.mq.topic_detail:cli'), - ('messaging:topic-list', 'SoftLayer.CLI.mq.topic_list:cli'), - ('messaging:topic-push', 'SoftLayer.CLI.mq.topic_push:cli'), - ('messaging:topic-remove', 'SoftLayer.CLI.mq.topic_remove:cli'), - ('messaging:topic-subscribe', 'SoftLayer.CLI.mq.topic_subscribe:cli'), - ('messaging:topic-unsubscribe', 'SoftLayer.CLI.mq.topic_unsubscribe:cli'), - ('metadata', 'SoftLayer.CLI.metadata:cli'), ('nas', 'SoftLayer.CLI.nas'), diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index d93b810de..5c489345d 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -18,7 +18,6 @@ from SoftLayer.managers.image import ImageManager from SoftLayer.managers.ipsec import IPSECManager from SoftLayer.managers.load_balancer import LoadBalancerManager -from SoftLayer.managers.messaging import MessagingManager from SoftLayer.managers.metadata import MetadataManager from SoftLayer.managers.network import NetworkManager from SoftLayer.managers.object_storage import ObjectStorageManager @@ -44,7 +43,6 @@ 'ImageManager', 'IPSECManager', 'LoadBalancerManager', - 'MessagingManager', 'MetadataManager', 'NetworkManager', 'ObjectStorageManager', diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py deleted file mode 100644 index 3ce1c17aa..000000000 --- a/SoftLayer/managers/messaging.py +++ /dev/null @@ -1,405 +0,0 @@ -""" - SoftLayer.messaging - ~~~~~~~~~~~~~~~~~~~ - Manager for the SoftLayer Message Queue service - - :license: MIT, see LICENSE for more details. -""" -import json - -import requests.auth - -from SoftLayer import consts -from SoftLayer import exceptions -# pylint: disable=no-self-use - - -ENDPOINTS = { - "dal05": { - "public": "dal05.mq.softlayer.net", - "private": "dal05.mq.service.networklayer.com" - } -} - - -class QueueAuth(requests.auth.AuthBase): - """SoftLayer Message Queue authentication for requests. - - :param endpoint: endpoint URL - :param username: SoftLayer username - :param api_key: SoftLayer API Key - :param auth_token: (optional) Starting auth token - """ - - def __init__(self, endpoint, username, api_key, auth_token=None): - self.endpoint = endpoint - self.username = username - self.api_key = api_key - self.auth_token = auth_token - - def auth(self): - """Authenticate.""" - headers = { - 'X-Auth-User': self.username, - 'X-Auth-Key': self.api_key - } - resp = requests.post(self.endpoint, headers=headers) - if resp.ok: - self.auth_token = resp.headers['X-Auth-Token'] - else: - raise exceptions.Unauthenticated("Error while authenticating: %s" - % resp.status_code) - - def handle_error(self, resp, **_): - """Handle errors.""" - resp.request.deregister_hook('response', self.handle_error) - if resp.status_code == 503: - resp.connection.send(resp.request) - elif resp.status_code == 401: - self.auth() - resp.request.headers['X-Auth-Token'] = self.auth_token - resp.connection.send(resp.request) - - def __call__(self, resp): - """Attach auth token to the request. - - Do authentication if an auth token isn't available - """ - if not self.auth_token: - self.auth() - resp.register_hook('response', self.handle_error) - resp.headers['X-Auth-Token'] = self.auth_token - return resp - - -class MessagingManager(object): - """Manage SoftLayer Message Queue accounts. - - See product information here: http://www.softlayer.com/message-queue - - :param SoftLayer.API.BaseClient client: the client instance - - """ - - def __init__(self, client): - self.client = client - - def list_accounts(self, **kwargs): - """List message queue accounts. - - :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) - """ - if 'mask' not in kwargs: - items = [ - 'id', - 'name', - 'status', - 'nodes', - ] - kwargs['mask'] = "mask[%s]" % ','.join(items) - - return self.client['Account'].getMessageQueueAccounts(**kwargs) - - def get_endpoint(self, datacenter=None, network=None): - """Get a message queue endpoint based on datacenter/network type. - - :param datacenter: datacenter code - :param network: network ('public' or 'private') - """ - if datacenter is None: - datacenter = 'dal05' - if network is None: - network = 'public' - try: - host = ENDPOINTS[datacenter][network] - return "https://%s" % host - except KeyError: - raise TypeError('Invalid endpoint %s/%s' - % (datacenter, network)) - - def get_endpoints(self): - """Get all known message queue endpoints.""" - return ENDPOINTS - - def get_connection(self, account_id, datacenter=None, network=None): - """Get connection to Message Queue Service. - - :param account_id: Message Queue Account id - :param datacenter: Datacenter code - :param network: network ('public' or 'private') - """ - if any([not self.client.auth, - not getattr(self.client.auth, 'username', None), - not getattr(self.client.auth, 'api_key', None)]): - raise exceptions.SoftLayerError( - 'Client instance auth must be BasicAuthentication.') - - client = MessagingConnection( - account_id, endpoint=self.get_endpoint(datacenter, network)) - client.authenticate(self.client.auth.username, - self.client.auth.api_key) - return client - - def ping(self, datacenter=None, network=None): - """Ping a message queue endpoint.""" - resp = requests.get('%s/v1/ping' % - self.get_endpoint(datacenter, network)) - resp.raise_for_status() - return True - - -class MessagingConnection(object): - """Message Queue Service Connection. - - :param account_id: Message Queue Account id - :param endpoint: Endpoint URL - """ - - def __init__(self, account_id, endpoint=None): - self.account_id = account_id - self.endpoint = endpoint - self.auth = None - - def _make_request(self, method, path, **kwargs): - """Make request. Generally not called directly. - - :param method: HTTP Method - :param path: resource Path - :param dict \\*\\*kwargs: extra request arguments - """ - headers = { - 'Content-Type': 'application/json', - 'User-Agent': consts.USER_AGENT, - } - headers.update(kwargs.get('headers', {})) - kwargs['headers'] = headers - kwargs['auth'] = self.auth - - url = '/'.join((self.endpoint, 'v1', self.account_id, path)) - resp = requests.request(method, url, **kwargs) - try: - resp.raise_for_status() - except requests.HTTPError as ex: - content = json.loads(ex.response.content) - raise exceptions.SoftLayerAPIError(ex.response.status_code, - content['message']) - return resp - - def authenticate(self, username, api_key, auth_token=None): - """Authenticate this connection using the given credentials. - - :param username: SoftLayer username - :param api_key: SoftLayer API Key - :param auth_token: (optional) Starting auth token - """ - auth_endpoint = '/'.join((self.endpoint, 'v1', - self.account_id, 'auth')) - auth = QueueAuth(auth_endpoint, username, api_key, - auth_token=auth_token) - auth.auth() - self.auth = auth - - def stats(self, period='hour'): - """Get account stats. - - :param period: 'hour', 'day', 'week', 'month' - """ - resp = self._make_request('get', 'stats/%s' % period) - return resp.json() - - # QUEUE METHODS - - def get_queues(self, tags=None): - """Get listing of queues. - - :param list tags: (optional) list of tags to filter by - """ - params = {} - if tags: - params['tags'] = ','.join(tags) - resp = self._make_request('get', 'queues', params=params) - return resp.json() - - def create_queue(self, queue_name, **kwargs): - """Create Queue. - - :param queue_name: Queue Name - :param dict \\*\\*kwargs: queue options - """ - queue = {} - queue.update(kwargs) - data = json.dumps(queue) - resp = self._make_request('put', 'queues/%s' % queue_name, data=data) - return resp.json() - - def modify_queue(self, queue_name, **kwargs): - """Modify Queue. - - :param queue_name: Queue Name - :param dict \\*\\*kwargs: queue options - """ - return self.create_queue(queue_name, **kwargs) - - def get_queue(self, queue_name): - """Get queue details. - - :param queue_name: Queue Name - """ - resp = self._make_request('get', 'queues/%s' % queue_name) - return resp.json() - - def delete_queue(self, queue_name, force=False): - """Delete Queue. - - :param queue_name: Queue Name - :param force: (optional) Force queue to be deleted even if there - are pending messages - """ - params = {} - if force: - params['force'] = 1 - self._make_request('delete', 'queues/%s' % queue_name, params=params) - return True - - def push_queue_message(self, queue_name, body, **kwargs): - """Create Queue Message. - - :param queue_name: Queue Name - :param body: Message body - :param dict \\*\\*kwargs: Message options - """ - message = {'body': body} - message.update(kwargs) - resp = self._make_request('post', 'queues/%s/messages' % queue_name, - data=json.dumps(message)) - return resp.json() - - def pop_messages(self, queue_name, count=1): - """Pop messages from a queue. - - :param queue_name: Queue Name - :param count: (optional) number of messages to retrieve - """ - resp = self._make_request('get', 'queues/%s/messages' % queue_name, - params={'batch': count}) - return resp.json() - - def pop_message(self, queue_name): - """Pop a single message from a queue. - - If no messages are returned this returns None - - :param queue_name: Queue Name - """ - messages = self.pop_messages(queue_name, count=1) - if messages['item_count'] > 0: - return messages['items'][0] - else: - return None - - def delete_message(self, queue_name, message_id): - """Delete a message. - - :param queue_name: Queue Name - :param message_id: Message id - """ - self._make_request('delete', 'queues/%s/messages/%s' - % (queue_name, message_id)) - return True - - # TOPIC METHODS - - def get_topics(self, tags=None): - """Get listing of topics. - - :param list tags: (optional) list of tags to filter by - """ - params = {} - if tags: - params['tags'] = ','.join(tags) - resp = self._make_request('get', 'topics', params=params) - return resp.json() - - def create_topic(self, topic_name, **kwargs): - """Create Topic. - - :param topic_name: Topic Name - :param dict \\*\\*kwargs: Topic options - """ - data = json.dumps(kwargs) - resp = self._make_request('put', 'topics/%s' % topic_name, data=data) - return resp.json() - - def modify_topic(self, topic_name, **kwargs): - """Modify Topic. - - :param topic_name: Topic Name - :param dict \\*\\*kwargs: Topic options - """ - return self.create_topic(topic_name, **kwargs) - - def get_topic(self, topic_name): - """Get topic details. - - :param topic_name: Topic Name - """ - resp = self._make_request('get', 'topics/%s' % topic_name) - return resp.json() - - def delete_topic(self, topic_name, force=False): - """Delete Topic. - - :param topic_name: Topic Name - :param force: (optional) Force topic to be deleted even if there - are attached subscribers - """ - params = {} - if force: - params['force'] = 1 - self._make_request('delete', 'topics/%s' % topic_name, params=params) - return True - - def push_topic_message(self, topic_name, body, **kwargs): - """Create Topic Message. - - :param topic_name: Topic Name - :param body: Message body - :param dict \\*\\*kwargs: Topic message options - """ - message = {'body': body} - message.update(kwargs) - resp = self._make_request('post', 'topics/%s/messages' % topic_name, - data=json.dumps(message)) - return resp.json() - - def get_subscriptions(self, topic_name): - """Listing of subscriptions on a topic. - - :param topic_name: Topic Name - """ - resp = self._make_request('get', - 'topics/%s/subscriptions' % topic_name) - return resp.json() - - def create_subscription(self, topic_name, subscription_type, **kwargs): - """Create Subscription. - - :param topic_name: Topic Name - :param subscription_type: type ('queue' or 'http') - :param dict \\*\\*kwargs: Subscription options - """ - resp = self._make_request( - 'post', 'topics/%s/subscriptions' % topic_name, - data=json.dumps({ - 'endpoint_type': subscription_type, 'endpoint': kwargs})) - return resp.json() - - def delete_subscription(self, topic_name, subscription_id): - """Delete a subscription. - - :param topic_name: Topic Name - :param subscription_id: Subscription id - """ - self._make_request('delete', 'topics/%s/subscriptions/%s' % - (topic_name, subscription_id)) - return True diff --git a/tests/managers/queue_tests.py b/tests/managers/queue_tests.py deleted file mode 100644 index beecaf616..000000000 --- a/tests/managers/queue_tests.py +++ /dev/null @@ -1,467 +0,0 @@ -""" - SoftLayer.tests.managers.queue_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -import mock - -import SoftLayer -from SoftLayer import consts -from SoftLayer.managers import messaging -from SoftLayer import testing - -QUEUE_1 = { - 'expiration': 40000, - 'message_count': 0, - 'name': 'example_queue', - 'tags': ['tag1', 'tag2', 'tag3'], - 'visibility_interval': 10, - 'visible_message_count': 0} -QUEUE_LIST = {'item_count': 1, 'items': [QUEUE_1]} -MESSAGE_1 = { - 'body': '', - 'fields': {'field': 'value'}, - 'id': 'd344a01133b61181f57d9950a852eb10', - 'initial_entry_time': 1343402631.3917992, - 'message': 'Object created', - 'visibility_delay': 0, - 'visibility_interval': 30000} -MESSAGE_POP = { - 'item_count': 1, - 'items': [MESSAGE_1], -} -MESSAGE_POP_EMPTY = { - 'item_count': 0, - 'items': [] -} - -TOPIC_1 = {'name': 'example_topic', 'tags': ['tag1', 'tag2', 'tag3']} -TOPIC_LIST = {'item_count': 1, 'items': [TOPIC_1]} -SUBSCRIPTION_1 = { - 'endpoint': { - 'account_id': 'test', - 'queue_name': 'topic_subscription_queue'}, - 'endpoint_type': 'queue', - 'id': 'd344a01133b61181f57d9950a85704d4', - 'message': 'Object created'} -SUBSCRIPTION_LIST = {'item_count': 1, 'items': [SUBSCRIPTION_1]} - - -def mocked_auth_call(self): - self.auth_token = 'NEW_AUTH_TOKEN' - - -class QueueAuthTests(testing.TestCase): - def set_up(self): - self.auth = messaging.QueueAuth( - 'endpoint', 'username', 'api_key', auth_token='auth_token') - - def test_init(self): - auth = SoftLayer.managers.messaging.QueueAuth( - 'endpoint', 'username', 'api_key', auth_token='auth_token') - self.assertEqual(auth.endpoint, 'endpoint') - self.assertEqual(auth.username, 'username') - self.assertEqual(auth.api_key, 'api_key') - self.assertEqual(auth.auth_token, 'auth_token') - - @mock.patch('SoftLayer.managers.messaging.requests.post') - def test_auth(self, post): - post().headers = {'X-Auth-Token': 'NEW_AUTH_TOKEN'} - post().ok = True - self.auth.auth() - self.auth.auth_token = 'NEW_AUTH_TOKEN' - - post().ok = False - self.assertRaises(SoftLayer.Unauthenticated, self.auth.auth) - - @mock.patch('SoftLayer.managers.messaging.QueueAuth.auth', - mocked_auth_call) - def test_handle_error_200(self): - # No op on no error - request = mock.MagicMock() - request.status_code = 200 - self.auth.handle_error(request) - - self.assertEqual(self.auth.auth_token, 'auth_token') - self.assertFalse(request.request.send.called) - - @mock.patch('SoftLayer.managers.messaging.QueueAuth.auth', - mocked_auth_call) - def test_handle_error_503(self): - # Retry once more on 503 error - request = mock.MagicMock() - request.status_code = 503 - self.auth.handle_error(request) - - self.assertEqual(self.auth.auth_token, 'auth_token') - request.connection.send.assert_called_with(request.request) - - @mock.patch('SoftLayer.managers.messaging.QueueAuth.auth', - mocked_auth_call) - def test_handle_error_401(self): - # Re-auth on 401 - request = mock.MagicMock() - request.status_code = 401 - request.request.headers = {'X-Auth-Token': 'OLD_AUTH_TOKEN'} - self.auth.handle_error(request) - - self.assertEqual(self.auth.auth_token, 'NEW_AUTH_TOKEN') - request.connection.send.assert_called_with(request.request) - - @mock.patch('SoftLayer.managers.messaging.QueueAuth.auth', - mocked_auth_call) - def test_call_unauthed(self): - request = mock.MagicMock() - request.headers = {} - self.auth.auth_token = None - self.auth(request) - - self.assertEqual(self.auth.auth_token, 'NEW_AUTH_TOKEN') - request.register_hook.assert_called_with( - 'response', self.auth.handle_error) - self.assertEqual(request.headers, {'X-Auth-Token': 'NEW_AUTH_TOKEN'}) - - -class MessagingManagerTests(testing.TestCase): - - def set_up(self): - self.client = mock.MagicMock() - self.manager = SoftLayer.MessagingManager(self.client) - - def test_list_accounts(self): - self.manager.list_accounts() - self.client['Account'].getMessageQueueAccounts.assert_called_with( - mask=mock.ANY) - - def test_get_endpoints(self): - endpoints = self.manager.get_endpoints() - self.assertEqual(endpoints, SoftLayer.managers.messaging.ENDPOINTS) - - @mock.patch('SoftLayer.managers.messaging.ENDPOINTS', { - 'datacenter01': { - 'private': 'private_endpoint', 'public': 'public_endpoint'}, - 'dal05': { - 'private': 'dal05_private', 'public': 'dal05_public'}}) - def test_get_endpoint(self): - # Defaults to dal05, public - endpoint = self.manager.get_endpoint() - self.assertEqual(endpoint, 'https://dal05_public') - - endpoint = self.manager.get_endpoint(network='private') - self.assertEqual(endpoint, 'https://dal05_private') - - endpoint = self.manager.get_endpoint(datacenter='datacenter01') - self.assertEqual(endpoint, 'https://public_endpoint') - - endpoint = self.manager.get_endpoint(datacenter='datacenter01', - network='private') - self.assertEqual(endpoint, 'https://private_endpoint') - - endpoint = self.manager.get_endpoint(datacenter='datacenter01', - network='private') - self.assertEqual(endpoint, 'https://private_endpoint') - - # ERROR CASES - self.assertRaises( - TypeError, - self.manager.get_endpoint, datacenter='doesnotexist') - - self.assertRaises( - TypeError, - self.manager.get_endpoint, network='doesnotexist') - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection') - def test_get_connection(self, conn): - queue_conn = self.manager.get_connection('QUEUE_ACCOUNT_ID') - conn.assert_called_with( - 'QUEUE_ACCOUNT_ID', endpoint='https://dal05.mq.softlayer.net') - conn().authenticate.assert_called_with( - self.client.auth.username, self.client.auth.api_key) - self.assertEqual(queue_conn, conn()) - - def test_get_connection_no_auth(self): - self.client.auth = None - self.assertRaises(SoftLayer.SoftLayerError, - self.manager.get_connection, 'QUEUE_ACCOUNT_ID') - - def test_get_connection_no_username(self): - self.client.auth.username = None - self.assertRaises(SoftLayer.SoftLayerError, - self.manager.get_connection, 'QUEUE_ACCOUNT_ID') - - def test_get_connection_no_api_key(self): - self.client.auth.api_key = None - self.assertRaises(SoftLayer.SoftLayerError, - self.manager.get_connection, 'QUEUE_ACCOUNT_ID') - - @mock.patch('SoftLayer.managers.messaging.requests.get') - def test_ping(self, get): - result = self.manager.ping() - - get.assert_called_with('https://dal05.mq.softlayer.net/v1/ping') - get().raise_for_status.assert_called_with() - self.assertTrue(result) - - -class MessagingConnectionTests(testing.TestCase): - - def set_up(self): - self.conn = SoftLayer.managers.messaging.MessagingConnection( - 'acount_id', endpoint='endpoint') - self.auth = mock.MagicMock() - self.conn.auth = self.auth - - def test_init(self): - self.assertEqual(self.conn.account_id, 'acount_id') - self.assertEqual(self.conn.endpoint, 'endpoint') - self.assertEqual(self.conn.auth, self.auth) - - @mock.patch('SoftLayer.managers.messaging.requests.request') - def test_make_request(self, request): - resp = self.conn._make_request('GET', 'path') - request.assert_called_with( - 'GET', 'endpoint/v1/acount_id/path', - headers={ - 'Content-Type': 'application/json', - 'User-Agent': consts.USER_AGENT}, - auth=self.auth) - request().raise_for_status.assert_called_with() - self.assertEqual(resp, request()) - - @mock.patch('SoftLayer.managers.messaging.QueueAuth') - def test_authenticate(self, auth): - self.conn.authenticate('username', 'api_key', auth_token='auth_token') - - auth.assert_called_with( - 'endpoint/v1/acount_id/auth', 'username', 'api_key', - auth_token='auth_token') - auth().auth.assert_called_with() - self.assertEqual(self.conn.auth, auth()) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_stats(self, make_request): - content = { - 'notifications': [{'key': [2012, 7, 27, 14, 31], 'value': 2}], - 'requests': [{'key': [2012, 7, 27, 14, 31], 'value': 11}]} - make_request().json.return_value = content - result = self.conn.stats() - - make_request.assert_called_with('get', 'stats/hour') - self.assertEqual(content, result) - - # Queue-based Tests - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_get_queues(self, make_request): - make_request().json.return_value = QUEUE_LIST - result = self.conn.get_queues() - - make_request.assert_called_with('get', 'queues', params={}) - self.assertEqual(QUEUE_LIST, result) - - # with tags - result = self.conn.get_queues(tags=['tag1', 'tag2']) - - make_request.assert_called_with( - 'get', 'queues', params={'tags': 'tag1,tag2'}) - self.assertEqual(QUEUE_LIST, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_create_queue(self, make_request): - make_request().json.return_value = QUEUE_1 - result = self.conn.create_queue('example_queue') - - make_request.assert_called_with( - 'put', 'queues/example_queue', data='{}') - self.assertEqual(QUEUE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_modify_queue(self, make_request): - make_request().json.return_value = QUEUE_1 - result = self.conn.modify_queue('example_queue') - - make_request.assert_called_with( - 'put', 'queues/example_queue', data='{}') - self.assertEqual(QUEUE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_get_queue(self, make_request): - make_request().json.return_value = QUEUE_1 - result = self.conn.get_queue('example_queue') - - make_request.assert_called_with('get', 'queues/example_queue') - self.assertEqual(QUEUE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_delete_queue(self, make_request): - result = self.conn.delete_queue('example_queue') - make_request.assert_called_with( - 'delete', 'queues/example_queue', params={}) - self.assertTrue(result) - - # With Force - result = self.conn.delete_queue('example_queue', force=True) - make_request.assert_called_with( - 'delete', 'queues/example_queue', params={'force': 1}) - self.assertTrue(result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_push_queue_message(self, make_request): - make_request().json.return_value = MESSAGE_1 - result = self.conn.push_queue_message('example_queue', '') - - make_request.assert_called_with( - 'post', 'queues/example_queue/messages', data='{"body": ""}') - self.assertEqual(MESSAGE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_pop_messages(self, make_request): - make_request().json.return_value = MESSAGE_POP - result = self.conn.pop_messages('example_queue') - - make_request.assert_called_with( - 'get', 'queues/example_queue/messages', params={'batch': 1}) - self.assertEqual(MESSAGE_POP, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_pop_message(self, make_request): - make_request().json.return_value = MESSAGE_POP - result = self.conn.pop_message('example_queue') - - make_request.assert_called_with( - 'get', 'queues/example_queue/messages', params={'batch': 1}) - self.assertEqual(MESSAGE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_pop_message_empty(self, make_request): - make_request().json.return_value = MESSAGE_POP_EMPTY - result = self.conn.pop_message('example_queue') - - make_request.assert_called_with( - 'get', 'queues/example_queue/messages', params={'batch': 1}) - self.assertEqual(None, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_delete_message(self, make_request): - result = self.conn.delete_message('example_queue', MESSAGE_1['id']) - - make_request.assert_called_with( - 'delete', 'queues/example_queue/messages/%s' % MESSAGE_1['id']) - self.assertTrue(result) - - # Topic-based Tests - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_get_topics(self, make_request): - make_request().json.return_value = TOPIC_LIST - result = self.conn.get_topics() - - make_request.assert_called_with('get', 'topics', params={}) - self.assertEqual(TOPIC_LIST, result) - - # with tags - result = self.conn.get_topics(tags=['tag1', 'tag2']) - - make_request.assert_called_with( - 'get', 'topics', params={'tags': 'tag1,tag2'}) - self.assertEqual(TOPIC_LIST, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_create_topic(self, make_request): - make_request().json.return_value = TOPIC_1 - result = self.conn.create_topic('example_topic') - - make_request.assert_called_with( - 'put', 'topics/example_topic', data='{}') - self.assertEqual(TOPIC_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_modify_topic(self, make_request): - make_request().json.return_value = TOPIC_1 - result = self.conn.modify_topic('example_topic') - - make_request.assert_called_with( - 'put', 'topics/example_topic', data='{}') - self.assertEqual(TOPIC_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_get_topic(self, make_request): - make_request().json.return_value = TOPIC_1 - result = self.conn.get_topic('example_topic') - - make_request.assert_called_with('get', 'topics/example_topic') - self.assertEqual(TOPIC_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_delete_topic(self, make_request): - result = self.conn.delete_topic('example_topic') - make_request.assert_called_with( - 'delete', 'topics/example_topic', params={}) - self.assertTrue(result) - - # With Force - result = self.conn.delete_topic('example_topic', force=True) - make_request.assert_called_with( - 'delete', 'topics/example_topic', params={'force': 1}) - self.assertTrue(result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_push_topic_message(self, make_request): - make_request().json.return_value = MESSAGE_1 - result = self.conn.push_topic_message('example_topic', '') - - make_request.assert_called_with( - 'post', 'topics/example_topic/messages', data='{"body": ""}') - self.assertEqual(MESSAGE_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_get_subscriptions(self, make_request): - make_request().json.return_value = SUBSCRIPTION_LIST - result = self.conn.get_subscriptions('example_topic') - - make_request.assert_called_with( - 'get', 'topics/example_topic/subscriptions') - self.assertEqual(SUBSCRIPTION_LIST, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection' - '._make_request') - def test_create_subscription(self, make_request): - make_request().json.return_value = SUBSCRIPTION_1 - endpoint_details = { - 'account_id': 'test', - 'queue_name': 'topic_subscription_queue'} - result = self.conn.create_subscription( - 'example_topic', 'queue', **endpoint_details) - - make_request.assert_called_with( - 'post', 'topics/example_topic/subscriptions', data=mock.ANY) - self.assertEqual(SUBSCRIPTION_1, result) - - @mock.patch('SoftLayer.managers.messaging.MessagingConnection.' - '_make_request') - def test_delete_subscription(self, make_request): - make_request().json.return_value = SUBSCRIPTION_1 - result = self.conn.delete_subscription( - 'example_topic', SUBSCRIPTION_1['id']) - - make_request.assert_called_with( - 'delete', - 'topics/example_topic/subscriptions/%s' % SUBSCRIPTION_1['id']) - self.assertTrue(result) From edc71413887906215fb1b776a3e13b563bb6c04f Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Mon, 25 Feb 2019 20:07:03 -0600 Subject: [PATCH 0014/1582] Reflash Firmware CLI/Manager method --- SoftLayer/CLI/hardware/reflash_firmware.py | 26 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 1 + SoftLayer/managers/hardware.py | 24 +++++++++++++++++ tests/CLI/modules/server_tests.py | 11 ++++++++ tests/managers/hardware_tests.py | 18 +++++++++++++ 6 files changed, 81 insertions(+) create mode 100644 SoftLayer/CLI/hardware/reflash_firmware.py diff --git a/SoftLayer/CLI/hardware/reflash_firmware.py b/SoftLayer/CLI/hardware/reflash_firmware.py new file mode 100644 index 000000000..40d334169 --- /dev/null +++ b/SoftLayer/CLI/hardware/reflash_firmware.py @@ -0,0 +1,26 @@ +"""Reflash firmware.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Reflash server firmware.""" + + mgr = SoftLayer.HardwareManager(env.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') + if not (env.skip_confirmations or + formatting.confirm('This will power off the server with id %s and ' + 'reflash device firmware. Continue?' % hw_id)): + raise exceptions.CLIAbort('Aborted.') + + mgr.reflash_firmware(hw_id) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc6a86abe..f03b1e17d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -240,6 +240,7 @@ ('hardware:reload', 'SoftLayer.CLI.hardware.reload:cli'), ('hardware:credentials', 'SoftLayer.CLI.hardware.credentials:cli'), ('hardware:update-firmware', 'SoftLayer.CLI.hardware.update_firmware:cli'), + ('hardware:reflash-firmware', 'SoftLayer.CLI.hardware.reflash_firmware:cli'), ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 9a9587b81..72838692e 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -81,6 +81,7 @@ rebootDefault = True rebootHard = True createFirmwareUpdateTransaction = True +createFirmwareReflashTransaction = True setUserMetadata = ['meta'] reloadOperatingSystem = 'OK' getReverseDomainRecords = [ diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9f97a1d3d..3f8bdcd80 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -613,6 +613,30 @@ def update_firmware(self, return self.hardware.createFirmwareUpdateTransaction( bool(ipmi), bool(raid_controller), bool(bios), bool(hard_drive), id=hardware_id) + def reflash_firmware(self, + hardware_id, + ipmi=True, + raid_controller=True, + bios=True): + """Reflash hardware firmware. + + This will cause the server to be unavailable for ~20 minutes. + + :param int hardware_id: The ID of the hardware to have its firmware + updated. + :param bool ipmi: Update the ipmi firmware. + :param bool raid_controller: Update the raid controller firmware. + :param bool bios: Update the bios firmware.. + + Example:: + + # Check the servers active transactions to see progress + result = mgr.update_firmware(hardware_id=1234) + """ + + return self.hardware.createFirmwareReflashTransaction( + bool(ipmi), bool(raid_controller), bool(bios), id=hardware_id) + def wait_for_ready(self, instance_id, limit=14400, delay=10, pending=False): """Determine if a Server is ready. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index c9e030770..75e2cd78f 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -482,6 +482,17 @@ def test_update_firmware(self, confirm_mock): 'createFirmwareUpdateTransaction', args=((1, 1, 1, 1)), identifier=1000) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_reflash_firmware(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['server', 'reflash-firmware', '1000']) + + self.assert_no_fail(result) + self.assertEqual(result.output, "") + self.assert_called_with('SoftLayer_Hardware_Server', + 'createFirmwareReflashTransaction', + args=((1, 1, 1)), identifier=1000) + def test_edit(self): result = self.run_command(['server', 'edit', '--domain=example.com', diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index b3c95a1d2..608a0cdc4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -392,6 +392,24 @@ def test_update_firmware_selective(self): 'createFirmwareUpdateTransaction', identifier=100, args=(0, 1, 1, 0)) + def test_reflash_firmware(self): + result = self.hardware.update_firmware(100) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Hardware_Server', + 'createFirmwareReflashTransaction', + identifier=100, args=(1, 1, 1)) + + def test_reflash_firmware_selective(self): + result = self.hardware.update_firmware(100, + ipmi=False, + hard_drive=False) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Hardware_Server', + 'createFirmwareReflashTransaction', + identifier=100, args=(1, 0, 0)) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): From a9512de5943dfaadff1cb6137f1da8a458b5c8b4 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Mon, 25 Feb 2019 20:07:03 -0600 Subject: [PATCH 0015/1582] Updated firmware reflash documentation to be more clear --- SoftLayer/managers/hardware.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 3f8bdcd80..bb9d09e7f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -620,18 +620,19 @@ def reflash_firmware(self, bios=True): """Reflash hardware firmware. - This will cause the server to be unavailable for ~20 minutes. + This will cause the server to be unavailable for ~60 minutes. + The firmware will not be upgraded but rather reflashed to the version installed. :param int hardware_id: The ID of the hardware to have its firmware - updated. - :param bool ipmi: Update the ipmi firmware. - :param bool raid_controller: Update the raid controller firmware. - :param bool bios: Update the bios firmware.. + reflashed. + :param bool ipmi: Reflash the ipmi firmware. + :param bool raid_controller: Reflash the raid controller firmware. + :param bool bios: Reflash the bios firmware. Example:: # Check the servers active transactions to see progress - result = mgr.update_firmware(hardware_id=1234) + result = mgr.reflash_firmware(hardware_id=1234) """ return self.hardware.createFirmwareReflashTransaction( From d4c287baa1ef5c2da1104835accad04d2b842346 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Tue, 26 Feb 2019 20:16:53 -0600 Subject: [PATCH 0016/1582] Fixed broken unit tests --- tests/managers/hardware_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 608a0cdc4..c70d6ea46 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -393,7 +393,7 @@ def test_update_firmware_selective(self): identifier=100, args=(0, 1, 1, 0)) def test_reflash_firmware(self): - result = self.hardware.update_firmware(100) + result = self.hardware.reflash_firmware(100) self.assertEqual(result, True) self.assert_called_with('SoftLayer_Hardware_Server', @@ -401,7 +401,7 @@ def test_reflash_firmware(self): identifier=100, args=(1, 1, 1)) def test_reflash_firmware_selective(self): - result = self.hardware.update_firmware(100, + result = self.hardware.reflash_firmware(100, ipmi=False, hard_drive=False) From 7df54efc1c5a91fc5c6e64bebfe4e58d9e2c7b28 Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Tue, 26 Feb 2019 20:19:53 -0600 Subject: [PATCH 0017/1582] Fixed broken unit tests --- tests/managers/hardware_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index c70d6ea46..e9d1ee4a5 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -402,8 +402,8 @@ def test_reflash_firmware(self): def test_reflash_firmware_selective(self): result = self.hardware.reflash_firmware(100, - ipmi=False, - hard_drive=False) + raid_controller=False, + bios=False) self.assertEqual(result, True) self.assert_called_with('SoftLayer_Hardware_Server', From e6aa8068869de11cc378a63afb22b58c31e0848d Mon Sep 17 00:00:00 2001 From: Adam Shaw Date: Tue, 26 Feb 2019 20:26:47 -0600 Subject: [PATCH 0018/1582] Updating to adhere to PEP8 --- SoftLayer/managers/hardware.py | 8 ++++---- tests/managers/hardware_tests.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index bb9d09e7f..77c4e604c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -614,10 +614,10 @@ def update_firmware(self, bool(ipmi), bool(raid_controller), bool(bios), bool(hard_drive), id=hardware_id) def reflash_firmware(self, - hardware_id, - ipmi=True, - raid_controller=True, - bios=True): + hardware_id, + ipmi=True, + raid_controller=True, + bios=True): """Reflash hardware firmware. This will cause the server to be unavailable for ~60 minutes. diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e9d1ee4a5..030d808d6 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -402,8 +402,8 @@ def test_reflash_firmware(self): def test_reflash_firmware_selective(self): result = self.hardware.reflash_firmware(100, - raid_controller=False, - bios=False) + raid_controller=False, + bios=False) self.assertEqual(result, True) self.assert_called_with('SoftLayer_Hardware_Server', From 8ef829cd63e9b52aa38161ce0448b3eebf5aa122 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 Feb 2019 21:04:40 -0600 Subject: [PATCH 0019/1582] #1089 fixed old docs --- docs/api/managers/messaging.rst | 5 ----- docs/cli.rst | 37 +++++++++++++++++---------------- docs/index.rst | 11 ++++------ 3 files changed, 23 insertions(+), 30 deletions(-) delete mode 100644 docs/api/managers/messaging.rst diff --git a/docs/api/managers/messaging.rst b/docs/api/managers/messaging.rst deleted file mode 100644 index f86bbc980..000000000 --- a/docs/api/managers/messaging.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _messaging: - -.. automodule:: SoftLayer.managers.messaging - :members: - :inherited-members: diff --git a/docs/cli.rst b/docs/cli.rst index 7701dc625..bf79aa697 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -60,43 +60,43 @@ Usage Examples To discover the available commands, simply type `slcli`. :: - $ slcli + $ slcli Usage: slcli [OPTIONS] COMMAND [ARGS]... - + SoftLayer Command-line Client - + Options: - --format [table|raw|json|jsonraw] - Output format [default: table] - -C, --config PATH Config file location [default: - ~/.softlayer] - -v, --verbose Sets the debug noise level, specify multiple - times for more verbosity. - --proxy TEXT HTTP[S] proxy to be use to make API calls - -y, --really / --not-really Confirm all prompt actions - --demo / --no-demo Use demo data instead of actually making API - calls - --version Show the version and exit. - -h, --help Show this message and exit. - + --format [table|raw|json|jsonraw] Output format [default: raw] + -C, --config PATH Config file location [default: ~\.softlayer] + -v, --verbose Sets the debug noise level, specify multiple times for more verbosity. + --proxy TEXT HTTP[S] proxy to be use to make API calls + -y, --really / --not-really Confirm all prompt actions + --demo / --no-demo Use demo data instead of actually making API calls + --version Show the version and exit. + -h, --help Show this message and exit. + Commands: block Block Storage. call-api Call arbitrary API endpoints. cdn Content Delivery Network. config CLI configuration. + dedicatedhost Dedicated Host. dns Domain Name System. + event-log Event Logs. file File Storage. firewall Firewalls. globalip Global IP addresses. hardware Hardware servers. image Compute images. + ipsec IPSEC VPN loadbal Load balancers. - messaging Message queue service. metadata Find details about this machine. nas Network Attached Storage. object-storage Object Storage. + order View and order from the catalog. report Reports. rwhois Referral Whois. + securitygroup Network security groups. setup Edit configuration. shell Enters a shell for slcli. sshkey SSH Keys. @@ -104,9 +104,10 @@ To discover the available commands, simply type `slcli`. subnet Network subnets. summary Account summary. ticket Support tickets. + user Manage Users. virtual Virtual Servers. vlan Network VLANs. - + To use most commands your SoftLayer username and api_key need to be configured. The easiest way to do that is to use: 'slcli setup' diff --git a/docs/index.rst b/docs/index.rst index d4bf7dd70..1b3c2b390 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,17 +2,15 @@ SoftLayer API Python Client |version| ======================================== -`API Docs `_ ``|`` +`API Docs `_ ``|`` `GitHub `_ ``|`` `Issues `_ ``|`` `Pull Requests `_ ``|`` `PyPI `_ ``|`` -`Twitter `_ ``|`` -`#softlayer on freenode `_ This is the documentation to SoftLayer's Python API Bindings. These bindings -use SoftLayer's `XML-RPC interface `_ +use SoftLayer's `XML-RPC interface `_ in order to manage SoftLayer services. .. toctree:: @@ -38,10 +36,9 @@ Contributing External Links -------------- -* `SoftLayer API Documentation `_ +* `SoftLayer API Documentation `_ * `Source on GitHub `_ * `Issues `_ * `Pull Requests `_ * `PyPI `_ -* `Twitter `_ -* `#softlayer on freenode `_ + From 7506faf79aed891000b93c8956b0daa2109dbc83 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 Feb 2019 21:23:35 -0600 Subject: [PATCH 0020/1582] 5.7.1 release --- CHANGELOG.md | 9 +++++++-- SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1d3c128..cb06a9ec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,13 @@ # Change Log +## [5.7.1] - 2019-02-26 +- https://github.com/softlayer/softlayer-python/compare/v5.7.0...v5.7.1 -## [5.7.0] - 2018-11-16 -- Changes: https://github.com/softlayer/softlayer-python/compare/v5.6.4...master ++ #1089 removed legacy SL message queue commands ++ Support for Hardware reflash firmware CLI/Manager method + +## [5.7.0] - 2019-02-15 +- Changes: https://github.com/softlayer/softlayer-python/compare/v5.6.4...v5.7.0 + #1099 Support for security group Ids + event-log cli command diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 0400a719c..f3120e27e 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.7.0' +VERSION = 'v5.7.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 e0ff8b169..12835570f 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.7.0', + version='5.7.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ce6931ed3..c464f9693 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.7.0+git' # check versioning +version: '5.7.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From a2ce7926d72c5445399bc348dd18296e04774659 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 27 Feb 2019 17:06:36 -0600 Subject: [PATCH 0021/1582] ground work for some account/billing features --- SoftLayer/CLI/account/__init__.py | 1 + SoftLayer/CLI/account/invoice_detail.py | 20 ++++++++++++++++++++ SoftLayer/CLI/account/invoice_list.py | 21 +++++++++++++++++++++ SoftLayer/CLI/account/maintenance.py | 20 ++++++++++++++++++++ SoftLayer/CLI/account/summary.py | 24 ++++++++++++++++++++++++ SoftLayer/CLI/user/orders.py | 20 ++++++++++++++++++++ 6 files changed, 106 insertions(+) create mode 100644 SoftLayer/CLI/account/__init__.py create mode 100644 SoftLayer/CLI/account/invoice_detail.py create mode 100644 SoftLayer/CLI/account/invoice_list.py create mode 100644 SoftLayer/CLI/account/maintenance.py create mode 100644 SoftLayer/CLI/account/summary.py create mode 100644 SoftLayer/CLI/user/orders.py diff --git a/SoftLayer/CLI/account/__init__.py b/SoftLayer/CLI/account/__init__.py new file mode 100644 index 000000000..50da7c7f0 --- /dev/null +++ b/SoftLayer/CLI/account/__init__.py @@ -0,0 +1 @@ +"""Account commands""" diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py new file mode 100644 index 000000000..695d68e66 --- /dev/null +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -0,0 +1,20 @@ +"""Invoice details""" +# :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 + + +@click.command() + +@environment.pass_env +def cli(env): + """Invoices and all that mess""" + + # Print a detail of upcoming invoice, or specified invoice + + # export to pdf/excel \ No newline at end of file diff --git a/SoftLayer/CLI/account/invoice_list.py b/SoftLayer/CLI/account/invoice_list.py new file mode 100644 index 000000000..9427b4f26 --- /dev/null +++ b/SoftLayer/CLI/account/invoice_list.py @@ -0,0 +1,21 @@ +"""Invoice listing""" +# :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 + + +@click.command() + +@environment.pass_env +def cli(env): + """Invoices and all that mess""" + + # List invoices + + # invoice id, total recurring, total one time, total other, summary of what was ordered + # 123, 5$, 0$, 0$, 1 hardware, 2 vsi, 1 storage, 1 vlan \ No newline at end of file diff --git a/SoftLayer/CLI/account/maintenance.py b/SoftLayer/CLI/account/maintenance.py new file mode 100644 index 000000000..9abf1d737 --- /dev/null +++ b/SoftLayer/CLI/account/maintenance.py @@ -0,0 +1,20 @@ +"""Account Maintance manager""" +# :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 + + +@click.command() + +@environment.pass_env +def cli(env): + """Summary and acknowledgement of upcoming and ongoing maintenance""" + + # Print a list of all on going maintenance + + # Allow ack all, or ack specific maintenance \ No newline at end of file diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py new file mode 100644 index 000000000..5a75863a1 --- /dev/null +++ b/SoftLayer/CLI/account/summary.py @@ -0,0 +1,24 @@ +"""Account Summary page""" +# :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 + + +@click.command() + +@environment.pass_env +def cli(env): + """Prints some various bits of information about an account""" + + #TODO + # account info + # # of servers, vsi, vlans, ips, per dc? + # next invoice details + # upcoming cancelations? + # tickets and events upcoming + diff --git a/SoftLayer/CLI/user/orders.py b/SoftLayer/CLI/user/orders.py new file mode 100644 index 000000000..12688f397 --- /dev/null +++ b/SoftLayer/CLI/user/orders.py @@ -0,0 +1,20 @@ +"""Users order details""" +# :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 + + +@click.command() + +@environment.pass_env +def cli(env): + """Lists each user and the servers they ordered""" + + # Table = [user name, fqdn, cost] + # maybe print ordered storage / network bits + # if given a single user id, just print detailed info from that user \ No newline at end of file From ba2ad90bcf090bca221f43ac3fd5917953b17373 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Feb 2019 12:15:02 -0600 Subject: [PATCH 0022/1582] #1068 doc updates --- SoftLayer/CLI/hardware/edit.py | 25 ++++++++++--------------- SoftLayer/managers/vs_placement.py | 8 ++++++-- docs/api/managers/vs_placement.rst | 5 +++++ docs/cli.rst | 5 +++-- docs/cli/hardware.rst | 3 +++ docs/conf.py | 5 +++-- 6 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 docs/api/managers/vs_placement.rst create mode 100644 docs/cli/hardware.rst diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index beca22b3a..e01c23190 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -12,25 +12,20 @@ @click.command() @click.argument('identifier') @click.option('--domain', '-D', help="Domain portion of the FQDN") -@click.option('--userfile', '-F', - help="Read userdata from file", - type=click.Path(exists=True, readable=True, resolve_path=True)) -@click.option('--tag', '-g', - multiple=True, +@click.option('--userfile', '-F', type=click.Path(exists=True, readable=True, resolve_path=True, + help="Read userdata from file")) +@click.option('--tag', '-g', multiple=True, help="Tags to set or empty string to remove all") @click.option('--hostname', '-H', help="Host portion of the FQDN") @click.option('--userdata', '-u', help="User defined metadata string") -@click.option('--public-speed', - help="Public port speed.", - default=None, - type=click.Choice(['0', '10', '100', '1000', '10000'])) -@click.option('--private-speed', - help="Private port speed.", - default=None, - type=click.Choice(['0', '10', '100', '1000', '10000'])) +@click.option('--public-speed', default=None, + type=click.Choice(['0', '10', '100', '1000', '10000', '-1'] + help="Public port speed. -1 is best speed available")) +@click.option('--private-speed', default=None, + type=click.Choice(['0', '10', '100', '1000', '10000', '-1'] + help="Private port speed. -1 is best speed available")) @environment.pass_env -def cli(env, identifier, domain, userfile, tag, hostname, userdata, - public_speed, private_speed): +def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed): """Edit hardware details.""" if userdata and userfile: diff --git a/SoftLayer/managers/vs_placement.py b/SoftLayer/managers/vs_placement.py index d40a845e9..7fefc3fde 100644 --- a/SoftLayer/managers/vs_placement.py +++ b/SoftLayer/managers/vs_placement.py @@ -50,13 +50,17 @@ def list(self, mask=None): def create(self, placement_object): """Creates a placement group - :param dictionary placement_object: Below are the fields you can specify, taken from - https://softlayer.github.io/reference/datatypes/SoftLayer_Virtual_PlacementGroup/ + A placement_object is defined as:: + placement_object = { 'backendRouterId': 12345, 'name': 'Test Name', 'ruleId': 12345 } + + - https://softlayer.github.io/reference/datatypes/SoftLayer_Virtual_PlacementGroup/ + + :param dictionary placement_object: """ return self.client.call('SoftLayer_Virtual_PlacementGroup', 'createObject', placement_object) diff --git a/docs/api/managers/vs_placement.rst b/docs/api/managers/vs_placement.rst new file mode 100644 index 000000000..d5898f1f0 --- /dev/null +++ b/docs/api/managers/vs_placement.rst @@ -0,0 +1,5 @@ +.. _vs_placement: + +.. automodule:: SoftLayer.managers.vs_placement + :members: + :inherited-members: diff --git a/docs/cli.rst b/docs/cli.rst index bf79aa697..6d8b18218 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -14,6 +14,7 @@ functionality not fully documented here. cli/ipsec cli/vs + cli/hardware cli/ordering cli/users @@ -34,7 +35,7 @@ To update the configuration, you can use `slcli setup`. :..............:..................................................................: : Username : username : : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3/ : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : :..............:..................................................................: Are you sure you want to write settings to "/home/me/.softlayer"? [y/N]: y @@ -47,7 +48,7 @@ To check the configuration, you can use `slcli config show`. :..............:..................................................................: : Username : username : : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3/ : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : :..............:..................................................................: diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst new file mode 100644 index 000000000..577de4d1a --- /dev/null +++ b/docs/cli/hardware.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.list:cli + :prog: slcli hw list + :show-nested: \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 8c885c2d2..9e4c5205f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,8 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', - 'sphinx.ext.viewcode'] + 'sphinx.ext.viewcode', + 'sphinx_click.ext'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -48,7 +49,7 @@ project = u'SoftLayer API Python Client' # Hack to avoid the "Redefining built-in 'copyright'" error from static # analysis tools -globals()['copyright'] = u'2017, SoftLayer Technologies, Inc.' +globals()['copyright'] = u'2019, SoftLayer Technologies, Inc.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 555dd702d747ec04f8a57377f98354b86931d4aa Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Feb 2019 18:05:44 -0600 Subject: [PATCH 0023/1582] slcli hw documentation udpates --- SoftLayer/CLI/hardware/edit.py | 14 ++++++-------- docs/cli/hardware.rst | 14 +++++++++++--- docs/cli/hardware/cancel-reasons.rst | 3 +++ docs/cli/hardware/cancel.rst | 3 +++ docs/cli/hardware/create-options.rst | 3 +++ docs/cli/hardware/create.rst | 6 ++++++ docs/cli/hardware/credentials.rst | 3 +++ docs/cli/hardware/detail.rst | 3 +++ docs/cli/hardware/edit.rst | 5 +++++ docs/cli/hardware/list.rst | 3 +++ docs/cli/hardware/power-cycle.rst | 3 +++ docs/cli/hardware/power-off.rst | 3 +++ docs/cli/hardware/power-on.rst | 3 +++ docs/cli/hardware/ready.rst | 3 +++ docs/cli/hardware/reboot.rst | 3 +++ docs/cli/hardware/reflash-firmware.rst | 6 ++++++ docs/cli/hardware/reload.rst | 3 +++ docs/cli/hardware/rescue.rst | 3 +++ docs/cli/hardware/toggle-ipmi.rst | 3 +++ docs/cli/hardware/update-firmware.rst | 6 ++++++ 20 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 docs/cli/hardware/cancel-reasons.rst create mode 100644 docs/cli/hardware/cancel.rst create mode 100644 docs/cli/hardware/create-options.rst create mode 100644 docs/cli/hardware/create.rst create mode 100644 docs/cli/hardware/credentials.rst create mode 100644 docs/cli/hardware/detail.rst create mode 100644 docs/cli/hardware/edit.rst create mode 100644 docs/cli/hardware/list.rst create mode 100644 docs/cli/hardware/power-cycle.rst create mode 100644 docs/cli/hardware/power-off.rst create mode 100644 docs/cli/hardware/power-on.rst create mode 100644 docs/cli/hardware/ready.rst create mode 100644 docs/cli/hardware/reboot.rst create mode 100644 docs/cli/hardware/reflash-firmware.rst create mode 100644 docs/cli/hardware/reload.rst create mode 100644 docs/cli/hardware/rescue.rst create mode 100644 docs/cli/hardware/toggle-ipmi.rst create mode 100644 docs/cli/hardware/update-firmware.rst diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index e01c23190..708d1463d 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -12,18 +12,16 @@ @click.command() @click.argument('identifier') @click.option('--domain', '-D', help="Domain portion of the FQDN") -@click.option('--userfile', '-F', type=click.Path(exists=True, readable=True, resolve_path=True, - help="Read userdata from file")) +@click.option('--userfile', '-F', type=click.Path(exists=True, readable=True, resolve_path=True), + help="Read userdata from file") @click.option('--tag', '-g', multiple=True, help="Tags to set or empty string to remove all") @click.option('--hostname', '-H', help="Host portion of the FQDN") @click.option('--userdata', '-u', help="User defined metadata string") -@click.option('--public-speed', default=None, - type=click.Choice(['0', '10', '100', '1000', '10000', '-1'] - help="Public port speed. -1 is best speed available")) -@click.option('--private-speed', default=None, - type=click.Choice(['0', '10', '100', '1000', '10000', '-1'] - help="Private port speed. -1 is best speed available")) +@click.option('--public-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), + help="Public port speed. -1 is best speed available") +@click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), + help="Private port speed. -1 is best speed available") @environment.pass_env def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed): """Edit hardware details.""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 577de4d1a..f4a848bb9 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -1,3 +1,11 @@ -.. click:: SoftLayer.CLI.hardware.list:cli - :prog: slcli hw list - :show-nested: \ No newline at end of file +.. _cli_hardware: + +Interacting with Hardware +============================== + + +.. toctree:: + :maxdepth: 1 + :glob: + + hardware/* diff --git a/docs/cli/hardware/cancel-reasons.rst b/docs/cli/hardware/cancel-reasons.rst new file mode 100644 index 000000000..21cf30ef6 --- /dev/null +++ b/docs/cli/hardware/cancel-reasons.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.cancel_reasons:cli + :prog: hw cancel-reasons + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/cancel.rst b/docs/cli/hardware/cancel.rst new file mode 100644 index 000000000..6843c0544 --- /dev/null +++ b/docs/cli/hardware/cancel.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.cancel:cli + :prog: hw cancel + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/create-options.rst b/docs/cli/hardware/create-options.rst new file mode 100644 index 000000000..535bc944d --- /dev/null +++ b/docs/cli/hardware/create-options.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.create_options:cli + :prog: hw create-options + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/create.rst b/docs/cli/hardware/create.rst new file mode 100644 index 000000000..6e8102d2c --- /dev/null +++ b/docs/cli/hardware/create.rst @@ -0,0 +1,6 @@ +.. click:: SoftLayer.CLI.hardware.create:cli + :prog: hw create + :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. \ No newline at end of file diff --git a/docs/cli/hardware/credentials.rst b/docs/cli/hardware/credentials.rst new file mode 100644 index 000000000..f16175ea7 --- /dev/null +++ b/docs/cli/hardware/credentials.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.credentials:cli + :prog: hw credentials + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/detail.rst b/docs/cli/hardware/detail.rst new file mode 100644 index 000000000..1f78111b5 --- /dev/null +++ b/docs/cli/hardware/detail.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.detail:cli + :prog: hw detail + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/edit.rst b/docs/cli/hardware/edit.rst new file mode 100644 index 000000000..2f40e4bd5 --- /dev/null +++ b/docs/cli/hardware/edit.rst @@ -0,0 +1,5 @@ +.. click:: SoftLayer.CLI.hardware.edit:cli + :prog: hw edit + :show-nested: + +When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. \ No newline at end of file diff --git a/docs/cli/hardware/list.rst b/docs/cli/hardware/list.rst new file mode 100644 index 000000000..175c96ccd --- /dev/null +++ b/docs/cli/hardware/list.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.list:cli + :prog: hw list + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-cycle.rst b/docs/cli/hardware/power-cycle.rst new file mode 100644 index 000000000..9959b14dd --- /dev/null +++ b/docs/cli/hardware/power-cycle.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.power:power_cycle + :prog: hw power-cycle + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-off.rst b/docs/cli/hardware/power-off.rst new file mode 100644 index 000000000..3a34cbfbf --- /dev/null +++ b/docs/cli/hardware/power-off.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.power:power_off + :prog: hw power-off + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-on.rst b/docs/cli/hardware/power-on.rst new file mode 100644 index 000000000..aff596242 --- /dev/null +++ b/docs/cli/hardware/power-on.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.power:power_on + :prog: hw power-on + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/ready.rst b/docs/cli/hardware/ready.rst new file mode 100644 index 000000000..8ef18946f --- /dev/null +++ b/docs/cli/hardware/ready.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.ready:cli + :prog: hw ready + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/reboot.rst b/docs/cli/hardware/reboot.rst new file mode 100644 index 000000000..c68c188c4 --- /dev/null +++ b/docs/cli/hardware/reboot.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.power:reboot + :prog: hw reboot + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/reflash-firmware.rst b/docs/cli/hardware/reflash-firmware.rst new file mode 100644 index 000000000..da0ffa3e1 --- /dev/null +++ b/docs/cli/hardware/reflash-firmware.rst @@ -0,0 +1,6 @@ +.. click:: SoftLayer.CLI.hardware.reflash_firmware:cli + :prog: hw reflash-firmware + :show-nested: + + +Reflash here means the current version of the firmware running on your server will be re-flashed onto the selected hardware. This does require a reboot. See `slcli hw update-firmware` if you want the newest version. \ No newline at end of file diff --git a/docs/cli/hardware/reload.rst b/docs/cli/hardware/reload.rst new file mode 100644 index 000000000..91ddc4247 --- /dev/null +++ b/docs/cli/hardware/reload.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.reload:cli + :prog: hw reload + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/rescue.rst b/docs/cli/hardware/rescue.rst new file mode 100644 index 000000000..7602eecd6 --- /dev/null +++ b/docs/cli/hardware/rescue.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.power:rescue + :prog: hw rescue + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/toggle-ipmi.rst b/docs/cli/hardware/toggle-ipmi.rst new file mode 100644 index 000000000..b92eacc3e --- /dev/null +++ b/docs/cli/hardware/toggle-ipmi.rst @@ -0,0 +1,3 @@ +.. click:: SoftLayer.CLI.hardware.toggle_ipmi:cli + :prog: hw toggle-ipmi + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/update-firmware.rst b/docs/cli/hardware/update-firmware.rst new file mode 100644 index 000000000..3c105d0bf --- /dev/null +++ b/docs/cli/hardware/update-firmware.rst @@ -0,0 +1,6 @@ +.. click:: SoftLayer.CLI.hardware.update_firmware:cli + :prog: hw update-firmware + :show-nested: + + +This function updates the firmware of a server. If already at the latest version, no software is installed. \ No newline at end of file From 1606f9a34e56d624d3e5568e22c948bd2e730f1c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Feb 2019 18:32:40 -0600 Subject: [PATCH 0024/1582] #1068 fixed a bunch of 'no-else-raise' warnings --- SoftLayer/CLI/block/access/authorize.py | 14 ++++---------- SoftLayer/CLI/file/access/authorize.py | 6 ++---- SoftLayer/CLI/hardware/edit.py | 2 +- SoftLayer/CLI/ticket/attach.py | 23 ++++++++--------------- SoftLayer/CLI/ticket/detach.py | 23 ++++++++--------------- SoftLayer/managers/vs.py | 9 ++++----- SoftLayer/managers/vs_placement.py | 4 ++-- SoftLayer/transports.py | 4 ++-- 8 files changed, 31 insertions(+), 54 deletions(-) diff --git a/SoftLayer/CLI/block/access/authorize.py b/SoftLayer/CLI/block/access/authorize.py index df76b60e6..bf2da3af3 100644 --- a/SoftLayer/CLI/block/access/authorize.py +++ b/SoftLayer/CLI/block/access/authorize.py @@ -14,8 +14,7 @@ @click.option('--virtual-id', '-v', multiple=True, help='The id of one SoftLayer_Virtual_Guest to authorize') @click.option('--ip-address-id', '-i', multiple=True, - help='The id of one SoftLayer_Network_Subnet_IpAddress' - ' to authorize') + help='The id of one SoftLayer_Network_Subnet_IpAddress to authorize') @click.option('--ip-address', multiple=True, help='An IP address to authorize') @environment.pass_env @@ -30,16 +29,11 @@ def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, ip_address): for ip_address_value in ip_address: ip_address_object = network_manager.ip_lookup(ip_address_value) if ip_address_object == "": - click.echo("IP Address not found on your account. " + - "Please confirm IP and try again.") + click.echo("IP Address not found on your account. Please confirm IP and try again.") raise exceptions.ArgumentError('Incorrect IP Address') - else: - ip_address_id_list.append(ip_address_object['id']) + ip_address_id_list.append(ip_address_object['id']) - block_manager.authorize_host_to_volume(volume_id, - hardware_id, - virtual_id, - ip_address_id_list) + block_manager.authorize_host_to_volume(volume_id, hardware_id, virtual_id, ip_address_id_list) # If no exception was raised, the command succeeded click.echo('The specified hosts were authorized to access %s' % volume_id) diff --git a/SoftLayer/CLI/file/access/authorize.py b/SoftLayer/CLI/file/access/authorize.py index 92fe03653..835f5995f 100644 --- a/SoftLayer/CLI/file/access/authorize.py +++ b/SoftLayer/CLI/file/access/authorize.py @@ -33,11 +33,9 @@ def cli(env, volume_id, hardware_id, virtual_id, ip_address_id, for ip_address_value in ip_address: ip_address_object = network_manager.ip_lookup(ip_address_value) if ip_address_object == "": - click.echo("IP Address not found on your account. " + - "Please confirm IP and try again.") + click.echo("IP Address not found on your account. Please confirm IP and try again.") raise exceptions.ArgumentError('Incorrect IP Address') - else: - ip_address_id_list.append(ip_address_object['id']) + ip_address_id_list.append(ip_address_object['id']) file_manager.authorize_host_to_volume(volume_id, hardware_id, diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index 708d1463d..e4aca4dcc 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -18,7 +18,7 @@ help="Tags to set or empty string to remove all") @click.option('--hostname', '-H', help="Host portion of the FQDN") @click.option('--userdata', '-u', help="User defined metadata string") -@click.option('--public-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), +@click.option('--public-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") diff --git a/SoftLayer/CLI/ticket/attach.py b/SoftLayer/CLI/ticket/attach.py index 98adaa65b..c3086659f 100644 --- a/SoftLayer/CLI/ticket/attach.py +++ b/SoftLayer/CLI/ticket/attach.py @@ -11,11 +11,9 @@ @click.command() @click.argument('identifier', type=int) -@click.option('--hardware', - 'hardware_identifier', +@click.option('--hardware', 'hardware_identifier', help="The identifier for hardware to attach") -@click.option('--virtual', - 'virtual_identifier', +@click.option('--virtual', 'virtual_identifier', help="The identifier for a virtual server to attach") @environment.pass_env def cli(env, identifier, hardware_identifier, virtual_identifier): @@ -23,20 +21,15 @@ def cli(env, identifier, hardware_identifier, virtual_identifier): ticket_mgr = SoftLayer.TicketManager(env.client) if hardware_identifier and virtual_identifier: - raise exceptions.ArgumentError( - "Cannot attach hardware and a virtual server at the same time") - elif hardware_identifier: + raise exceptions.ArgumentError("Cannot attach hardware and a virtual server at the same time") + + if hardware_identifier: hardware_mgr = SoftLayer.HardwareManager(env.client) - hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids, - hardware_identifier, - 'hardware') + hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids, hardware_identifier, 'hardware') ticket_mgr.attach_hardware(identifier, hardware_id) elif virtual_identifier: vs_mgr = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vs_mgr.resolve_ids, - virtual_identifier, - 'VS') + vs_id = helpers.resolve_id(vs_mgr.resolve_ids, virtual_identifier, 'VS') ticket_mgr.attach_virtual_server(identifier, vs_id) else: - raise exceptions.ArgumentError( - "Must have a hardware or virtual server identifier to attach") + raise exceptions.ArgumentError("Must have a hardware or virtual server identifier to attach") diff --git a/SoftLayer/CLI/ticket/detach.py b/SoftLayer/CLI/ticket/detach.py index 8c8cae058..94a6f72ea 100644 --- a/SoftLayer/CLI/ticket/detach.py +++ b/SoftLayer/CLI/ticket/detach.py @@ -11,11 +11,9 @@ @click.command() @click.argument('identifier', type=int) -@click.option('--hardware', - 'hardware_identifier', +@click.option('--hardware', 'hardware_identifier', help="The identifier for hardware to detach") -@click.option('--virtual', - 'virtual_identifier', +@click.option('--virtual', 'virtual_identifier', help="The identifier for a virtual server to detach") @environment.pass_env def cli(env, identifier, hardware_identifier, virtual_identifier): @@ -23,20 +21,15 @@ def cli(env, identifier, hardware_identifier, virtual_identifier): ticket_mgr = SoftLayer.TicketManager(env.client) if hardware_identifier and virtual_identifier: - raise exceptions.ArgumentError( - "Cannot detach hardware and a virtual server at the same time") - elif hardware_identifier: + raise exceptions.ArgumentError("Cannot detach hardware and a virtual server at the same time") + + if hardware_identifier: hardware_mgr = SoftLayer.HardwareManager(env.client) - hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids, - hardware_identifier, - 'hardware') + hardware_id = helpers.resolve_id(hardware_mgr.resolve_ids, hardware_identifier, 'hardware') ticket_mgr.detach_hardware(identifier, hardware_id) elif virtual_identifier: vs_mgr = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vs_mgr.resolve_ids, - virtual_identifier, - 'VS') + vs_id = helpers.resolve_id(vs_mgr.resolve_ids, virtual_identifier, 'VS') ticket_mgr.detach_virtual_server(identifier, vs_id) else: - raise exceptions.ArgumentError( - "Must have a hardware or virtual server identifier to detach") + raise exceptions.ArgumentError("Must have a hardware or virtual server identifier to detach") diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index a3f26126e..00b738d0c 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -428,14 +428,13 @@ def _create_network_components( if public_subnet: if public_vlan is None: raise exceptions.SoftLayerError("You need to specify a public_vlan with public_subnet") - else: - parameters['primaryNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(public_subnet)} + + parameters['primaryNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(public_subnet)} if private_subnet: if private_vlan is None: raise exceptions.SoftLayerError("You need to specify a private_vlan with private_subnet") - else: - parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = { - "id": int(private_subnet)} + + parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} return parameters diff --git a/SoftLayer/managers/vs_placement.py b/SoftLayer/managers/vs_placement.py index 7fefc3fde..d492b2a1e 100644 --- a/SoftLayer/managers/vs_placement.py +++ b/SoftLayer/managers/vs_placement.py @@ -57,10 +57,10 @@ def create(self, placement_object): 'name': 'Test Name', 'ruleId': 12345 } - + - https://softlayer.github.io/reference/datatypes/SoftLayer_Virtual_PlacementGroup/ - :param dictionary placement_object: + :param dictionary placement_object: """ return self.client.call('SoftLayer_Virtual_PlacementGroup', 'createObject', placement_object) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index eaee84716..56eb14e7c 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -402,8 +402,8 @@ def __call__(self, request): except ValueError as json_ex: if ex.response.text == "": raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - else: - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + + raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) raise exceptions.SoftLayerAPIError(ex.response.status_code, message) except requests.RequestException as ex: From 2e58734d9705075dbc685ea0ae59015a3d9e32ca Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 1 Mar 2019 14:16:21 -0400 Subject: [PATCH 0025/1582] Added exception to handle json parsing error --- SoftLayer/CLI/order/place.py | 5 ++++- SoftLayer/CLI/order/place_quote.py | 6 +++++- tests/CLI/modules/order_tests.py | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 6d51ab935..539370067 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -76,7 +76,10 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, manager = ordering.OrderingManager(env.client) if extras: - extras = json.loads(extras) + try: + extras = json.loads(extras) + except ValueError as err: + raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err.message)) args = (package_keyname, location, order_items) kwargs = {'preset_keyname': preset, diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index 3f5215c40..7d72cb747 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -6,6 +6,7 @@ import click from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering @@ -68,7 +69,10 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, manager = ordering.OrderingManager(env.client) if extras: - extras = json.loads(extras) + try: + extras = json.loads(extras) + except ValueError as err: + raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err.message)) args = (package_keyname, location, order_items) kwargs = {'preset_keyname': preset, diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index c35d6d380..3a010b51b 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -6,6 +6,7 @@ import json from SoftLayer import testing +from SoftLayer.CLI import exceptions class OrderTests(testing.TestCase): @@ -103,6 +104,13 @@ def test_place(self): 'status': 'APPROVED'}, json.loads(result.output)) + def test_place_extras_parameter_fail(self): + result = self.run_command(['-y', 'order', 'place', 'package', 'DALLAS13', 'ITEM1', + '--extras', '{"device":[']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_place_quote(self): order_date = '2018-04-04 07:39:20' expiration_date = '2018-05-04 07:39:20' @@ -132,6 +140,13 @@ def test_place_quote(self): 'status': 'PENDING'}, json.loads(result.output)) + def test_place_quote_extras_parameter_fail(self): + result = self.run_command(['-y', 'order', 'place-quote', 'package', 'DALLAS13', 'ITEM1', + '--extras', '{"device":[']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_verify_hourly(self): order_date = '2017-04-04 07:39:20' order = {'orderId': 1234, 'orderDate': order_date, From b00f68befd353cd2a3b8639298d756c656bb93d7 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 1 Mar 2019 16:55:57 -0400 Subject: [PATCH 0026/1582] 1107 fixed tox analysis and python 3 support --- SoftLayer/CLI/order/place.py | 2 +- SoftLayer/CLI/order/place_quote.py | 2 +- tests/CLI/modules/order_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 539370067..eacfce898 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -79,7 +79,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, try: extras = json.loads(extras) except ValueError as err: - raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err.message)) + raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err)) args = (package_keyname, location, order_items) kwargs = {'preset_keyname': preset, diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index 7d72cb747..c7bbe6265 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -72,7 +72,7 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, try: extras = json.loads(extras) except ValueError as err: - raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err.message)) + raise exceptions.CLIAbort("There was an error when parsing the --extras value: {}".format(err)) args = (package_keyname, location, order_items) kwargs = {'preset_keyname': preset, diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 3a010b51b..854690ac6 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -5,8 +5,8 @@ """ import json -from SoftLayer import testing from SoftLayer.CLI import exceptions +from SoftLayer import testing class OrderTests(testing.TestCase): From 32d0bbe7b53c00dd1e852042a30e6fe39cecf864 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 6 Mar 2019 15:41:59 -0600 Subject: [PATCH 0027/1582] moved hardware commands into a single file --- docs/cli/hardware.rst | 92 ++++++++++++++++++++++++-- docs/cli/hardware/cancel-reasons.rst | 3 - docs/cli/hardware/cancel.rst | 3 - docs/cli/hardware/create-options.rst | 3 - docs/cli/hardware/create.rst | 6 -- docs/cli/hardware/credentials.rst | 3 - docs/cli/hardware/detail.rst | 3 - docs/cli/hardware/edit.rst | 5 -- docs/cli/hardware/list.rst | 3 - docs/cli/hardware/power-cycle.rst | 3 - docs/cli/hardware/power-off.rst | 3 - docs/cli/hardware/power-on.rst | 3 - docs/cli/hardware/ready.rst | 3 - docs/cli/hardware/reboot.rst | 3 - docs/cli/hardware/reflash-firmware.rst | 6 -- docs/cli/hardware/reload.rst | 3 - docs/cli/hardware/rescue.rst | 3 - docs/cli/hardware/toggle-ipmi.rst | 3 - docs/cli/hardware/update-firmware.rst | 6 -- 19 files changed, 88 insertions(+), 69 deletions(-) delete mode 100644 docs/cli/hardware/cancel-reasons.rst delete mode 100644 docs/cli/hardware/cancel.rst delete mode 100644 docs/cli/hardware/create-options.rst delete mode 100644 docs/cli/hardware/create.rst delete mode 100644 docs/cli/hardware/credentials.rst delete mode 100644 docs/cli/hardware/detail.rst delete mode 100644 docs/cli/hardware/edit.rst delete mode 100644 docs/cli/hardware/list.rst delete mode 100644 docs/cli/hardware/power-cycle.rst delete mode 100644 docs/cli/hardware/power-off.rst delete mode 100644 docs/cli/hardware/power-on.rst delete mode 100644 docs/cli/hardware/ready.rst delete mode 100644 docs/cli/hardware/reboot.rst delete mode 100644 docs/cli/hardware/reflash-firmware.rst delete mode 100644 docs/cli/hardware/reload.rst delete mode 100644 docs/cli/hardware/rescue.rst delete mode 100644 docs/cli/hardware/toggle-ipmi.rst delete mode 100644 docs/cli/hardware/update-firmware.rst diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f4a848bb9..5b3f480b1 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -4,8 +4,92 @@ Interacting with Hardware ============================== -.. toctree:: - :maxdepth: 1 - :glob: +.. click:: SoftLayer.CLI.hardware.cancel_reasons:cli + :prog: hw cancel-reasons + :show-nested: + +.. click:: SoftLayer.CLI.hardware.cancel:cli + :prog: hw cancel + :show-nested: + +.. click:: SoftLayer.CLI.hardware.create_options:cli + :prog: hw create-options + :show-nested: + +.. click:: SoftLayer.CLI.hardware.create:cli + :prog: hw create + :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. + +.. click:: SoftLayer.CLI.hardware.credentials:cli + :prog: hw credentials + :show-nested: + + +.. click:: SoftLayer.CLI.hardware.detail:cli + :prog: hw detail + :show-nested: + + +.. click:: SoftLayer.CLI.hardware.edit:cli + :prog: hw edit + :show-nested: + +When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. + + +.. click:: SoftLayer.CLI.hardware.list:cli + :prog: hw list + :show-nested: + +.. click:: SoftLayer.CLI.hardware.power:power_cycle + :prog: hw power-cycle + :show-nested: + +.. click:: SoftLayer.CLI.hardware.power:power_off + :prog: hw power-off + :show-nested: + +.. click:: SoftLayer.CLI.hardware.power:power_on + :prog: hw power-on + :show-nested: + +.. click:: SoftLayer.CLI.hardware.power:reboot + :prog: hw reboot + :show-nested: + +.. click:: SoftLayer.CLI.hardware.reload:cli + :prog: hw reload + :show-nested: + +.. click:: SoftLayer.CLI.hardware.power:rescue + :prog: hw rescue + +.. click:: SoftLayer.CLI.hardware.reflash_firmware:cli + :prog: hw reflash-firmware + :show-nested: + + +Reflash here means the current version of the firmware running on your server will be re-flashed onto the selected hardware. This does require a reboot. See `slcli hw update-firmware` if you want the newest version. + +.. click:: SoftLayer.CLI.hardware.update_firmware:cli + :prog: hw update-firmware + :show-nested: + + +This function updates the firmware of a server. If already at the latest version, no software is installed. + +.. click:: SoftLayer.CLI.hardware.toggle_ipmi:cli + :prog: hw toggle-ipmi + :show-nested: + + + :show-nested: + + +.. click:: SoftLayer.CLI.hardware.ready:cli + :prog: hw ready + :show-nested: - hardware/* diff --git a/docs/cli/hardware/cancel-reasons.rst b/docs/cli/hardware/cancel-reasons.rst deleted file mode 100644 index 21cf30ef6..000000000 --- a/docs/cli/hardware/cancel-reasons.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.cancel_reasons:cli - :prog: hw cancel-reasons - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/cancel.rst b/docs/cli/hardware/cancel.rst deleted file mode 100644 index 6843c0544..000000000 --- a/docs/cli/hardware/cancel.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.cancel:cli - :prog: hw cancel - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/create-options.rst b/docs/cli/hardware/create-options.rst deleted file mode 100644 index 535bc944d..000000000 --- a/docs/cli/hardware/create-options.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.create_options:cli - :prog: hw create-options - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/create.rst b/docs/cli/hardware/create.rst deleted file mode 100644 index 6e8102d2c..000000000 --- a/docs/cli/hardware/create.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.create:cli - :prog: hw create - :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. \ No newline at end of file diff --git a/docs/cli/hardware/credentials.rst b/docs/cli/hardware/credentials.rst deleted file mode 100644 index f16175ea7..000000000 --- a/docs/cli/hardware/credentials.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.credentials:cli - :prog: hw credentials - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/detail.rst b/docs/cli/hardware/detail.rst deleted file mode 100644 index 1f78111b5..000000000 --- a/docs/cli/hardware/detail.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.detail:cli - :prog: hw detail - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/edit.rst b/docs/cli/hardware/edit.rst deleted file mode 100644 index 2f40e4bd5..000000000 --- a/docs/cli/hardware/edit.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.edit:cli - :prog: hw edit - :show-nested: - -When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. \ No newline at end of file diff --git a/docs/cli/hardware/list.rst b/docs/cli/hardware/list.rst deleted file mode 100644 index 175c96ccd..000000000 --- a/docs/cli/hardware/list.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.list:cli - :prog: hw list - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-cycle.rst b/docs/cli/hardware/power-cycle.rst deleted file mode 100644 index 9959b14dd..000000000 --- a/docs/cli/hardware/power-cycle.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.power:power_cycle - :prog: hw power-cycle - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-off.rst b/docs/cli/hardware/power-off.rst deleted file mode 100644 index 3a34cbfbf..000000000 --- a/docs/cli/hardware/power-off.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.power:power_off - :prog: hw power-off - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/power-on.rst b/docs/cli/hardware/power-on.rst deleted file mode 100644 index aff596242..000000000 --- a/docs/cli/hardware/power-on.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.power:power_on - :prog: hw power-on - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/ready.rst b/docs/cli/hardware/ready.rst deleted file mode 100644 index 8ef18946f..000000000 --- a/docs/cli/hardware/ready.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.ready:cli - :prog: hw ready - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/reboot.rst b/docs/cli/hardware/reboot.rst deleted file mode 100644 index c68c188c4..000000000 --- a/docs/cli/hardware/reboot.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.power:reboot - :prog: hw reboot - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/reflash-firmware.rst b/docs/cli/hardware/reflash-firmware.rst deleted file mode 100644 index da0ffa3e1..000000000 --- a/docs/cli/hardware/reflash-firmware.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.reflash_firmware:cli - :prog: hw reflash-firmware - :show-nested: - - -Reflash here means the current version of the firmware running on your server will be re-flashed onto the selected hardware. This does require a reboot. See `slcli hw update-firmware` if you want the newest version. \ No newline at end of file diff --git a/docs/cli/hardware/reload.rst b/docs/cli/hardware/reload.rst deleted file mode 100644 index 91ddc4247..000000000 --- a/docs/cli/hardware/reload.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.reload:cli - :prog: hw reload - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/rescue.rst b/docs/cli/hardware/rescue.rst deleted file mode 100644 index 7602eecd6..000000000 --- a/docs/cli/hardware/rescue.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.power:rescue - :prog: hw rescue - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/toggle-ipmi.rst b/docs/cli/hardware/toggle-ipmi.rst deleted file mode 100644 index b92eacc3e..000000000 --- a/docs/cli/hardware/toggle-ipmi.rst +++ /dev/null @@ -1,3 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.toggle_ipmi:cli - :prog: hw toggle-ipmi - :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware/update-firmware.rst b/docs/cli/hardware/update-firmware.rst deleted file mode 100644 index 3c105d0bf..000000000 --- a/docs/cli/hardware/update-firmware.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. click:: SoftLayer.CLI.hardware.update_firmware:cli - :prog: hw update-firmware - :show-nested: - - -This function updates the firmware of a server. If already at the latest version, no software is installed. \ No newline at end of file From 4bf1e5379ae5426ff14d06c6aa0bc9bb9515a156 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 6 Mar 2019 19:12:37 -0400 Subject: [PATCH 0028/1582] Fixed docs about placement groups --- CHANGELOG.md | 6 +++--- SoftLayer/CLI/virt/placementgroup/create.py | 2 +- SoftLayer/CLI/virt/placementgroup/create_options.py | 2 +- SoftLayer/CLI/virt/placementgroup/list.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f1d3c128..5e4f08d72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,11 @@ ``` slcli vs placementgroup --help Commands: - create Create a placement group - create-options List options for creating Reserved Capacity + create Create a placement group. + create-options List options for creating a placement group. delete Delete a placement group. detail View details of a placement group. - list List Reserved Capacity groups. + list List placement groups. ``` + #962 Rest Transport improvements. Properly handle HTTP exceptions instead of crashing. + #1090 removed power_state column option from "slcli server list" diff --git a/SoftLayer/CLI/virt/placementgroup/create.py b/SoftLayer/CLI/virt/placementgroup/create.py index af1fb8db5..3051f9ac3 100644 --- a/SoftLayer/CLI/virt/placementgroup/create.py +++ b/SoftLayer/CLI/virt/placementgroup/create.py @@ -15,7 +15,7 @@ help="The keyName or Id of the rule to govern this placement group.") @environment.pass_env def cli(env, **args): - """Create a placement group""" + """Create a placement group.""" manager = PlacementManager(env.client) backend_router_id = helpers.resolve_id(manager.get_backend_router_id_from_hostname, args.get('backend_router'), diff --git a/SoftLayer/CLI/virt/placementgroup/create_options.py b/SoftLayer/CLI/virt/placementgroup/create_options.py index 3107fc334..790664cbc 100644 --- a/SoftLayer/CLI/virt/placementgroup/create_options.py +++ b/SoftLayer/CLI/virt/placementgroup/create_options.py @@ -11,7 +11,7 @@ @click.command() @environment.pass_env def cli(env): - """List options for creating Reserved Capacity""" + """List options for creating a placement group.""" manager = PlacementManager(env.client) routers = manager.get_routers() diff --git a/SoftLayer/CLI/virt/placementgroup/list.py b/SoftLayer/CLI/virt/placementgroup/list.py index 365205e74..94f72af1d 100644 --- a/SoftLayer/CLI/virt/placementgroup/list.py +++ b/SoftLayer/CLI/virt/placementgroup/list.py @@ -10,7 +10,7 @@ @click.command() @environment.pass_env def cli(env): - """List Reserved Capacity groups.""" + """List placement groups.""" manager = PlacementManager(env.client) result = manager.list() table = formatting.Table( From 0e6fed3c6e32219834e2e5287b170a723f8ae0b8 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 7 Mar 2019 18:21:28 -0600 Subject: [PATCH 0029/1582] #1002 basic structure for invoice features --- SoftLayer/CLI/account/maintenance.py | 37 +++++++++++++++-- SoftLayer/CLI/account/summary.py | 34 ++++++++++++---- SoftLayer/CLI/routes.py | 6 +++ SoftLayer/managers/account.py | 61 ++++++++++++++++++++++++++++ SoftLayer/utils.py | 7 ++++ 5 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 SoftLayer/managers/account.py diff --git a/SoftLayer/CLI/account/maintenance.py b/SoftLayer/CLI/account/maintenance.py index 9abf1d737..e9532b041 100644 --- a/SoftLayer/CLI/account/maintenance.py +++ b/SoftLayer/CLI/account/maintenance.py @@ -1,13 +1,14 @@ """Account Maintance manager""" # :license: MIT, see LICENSE for more details. - +from pprint import pprint as pp import click import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting - +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils @click.command() @@ -16,5 +17,35 @@ def cli(env): """Summary and acknowledgement of upcoming and ongoing maintenance""" # Print a list of all on going maintenance + manager = AccountManager(env.client) + events = manager.get_upcoming_events() + env.fout(event_table(events)) + # pp(events) + + # Allow ack all, or ack specific maintenance - # Allow ack all, or ack specific maintenance \ No newline at end of file +def event_table(events): + table = formatting.Table([ + "Id", + "Start Date", + "End Date", + "Subject", + "Status", + "Acknowledged", + "Updates", + "Impacted Resources" + ], title="Upcoming Events") + table.align['Subject'] = 'l' + table.align['Impacted Resources'] = 'l' + for event in events: + table.add_row([ + event.get('id'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('endDate')), + event.get('subject'), + utils.lookup(event, 'statusCode', 'name'), + event.get('acknowledgedFlag'), + event.get('updateCount'), + event.get('impactedResourceCount') + ]) + return table \ No newline at end of file diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py index 5a75863a1..7cff82789 100644 --- a/SoftLayer/CLI/account/summary.py +++ b/SoftLayer/CLI/account/summary.py @@ -1,5 +1,6 @@ """Account Summary page""" # :license: MIT, see LICENSE for more details. +from pprint import pprint as pp import click @@ -7,7 +8,8 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting - +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils @click.command() @@ -15,10 +17,28 @@ def cli(env): """Prints some various bits of information about an account""" - #TODO - # account info - # # of servers, vsi, vlans, ips, per dc? - # next invoice details - # upcoming cancelations? - # tickets and events upcoming + manager = AccountManager(env.client) + summary = manager.get_summary() + env.fout(get_snapshot_table(summary)) + +def get_snapshot_table(account): + """Generates a table for printing account summary data""" + table = formatting.KeyValueTable(["Name", "Value"], title="Account Snapshot") + table.align['Name'] = 'r' + table.align['Value'] = 'l' + table.add_row(['Company Name', account.get('companyName', '-')]) + table.add_row(['Balance', utils.lookup(account, 'pendingInvoice', 'startingBalance')]) + table.add_row(['Upcoming Invoice', utils.lookup(account, 'pendingInvoice', 'invoiceTotalAmount')]) + table.add_row(['Image Templates', account.get('blockDeviceTemplateGroupCount', '-')]) + table.add_row(['Dedicated Hosts', account.get('dedicatedHostCount', '-')]) + table.add_row(['Hardware', account.get('hardwareCount', '-')]) + table.add_row(['Virtual Guests', account.get('virtualGuestCount', '-')]) + table.add_row(['Domains', account.get('domainCount', '-')]) + table.add_row(['Network Storage Volumes', account.get('networkStorageCount', '-')]) + table.add_row(['Open Tickets', account.get('openTicketCount', '-')]) + table.add_row(['Network Vlans', account.get('networkVlanCount', '-')]) + table.add_row(['Subnets', account.get('subnetCount', '-')]) + table.add_row(['Users', account.get('userCount', '-')]) + # table.add_row(['', account.get('', '-')]) + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc6a86abe..bb6bbad78 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -11,6 +11,12 @@ ('call-api', 'SoftLayer.CLI.call_api:cli'), + ('account', 'SoftLayer.CLI.account'), + ('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'), + ('account:invoice-list', 'SoftLayer.CLI.account.invoice_list:cli'), + ('account:maintenance', 'SoftLayer.CLI.account.maintenance:cli'), + ('account:summary', 'SoftLayer.CLI.account.summary:cli'), + ('virtual', 'SoftLayer.CLI.virt'), ('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'), ('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py new file mode 100644 index 000000000..8d1ecb176 --- /dev/null +++ b/SoftLayer/managers/account.py @@ -0,0 +1,61 @@ +""" + SoftLayer.account + ~~~~~~~~~~~~~~~~~~~~~~~ + Account manager + + :license: MIT, see License for more details. +""" + +import logging +import SoftLayer + +from SoftLayer import utils + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + +LOGGER = logging.getLogger(__name__) + + +class AccountManager(utils.IdentifierMixin, object): + """Common functions for getting information from the Account service + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.client = client + + def get_summary(self): + mask = """mask[ + nextInvoiceTotalAmount, + pendingInvoice[invoiceTotalAmount], + blockDeviceTemplateGroupCount, + dedicatedHostCount, + domainCount, + hardwareCount, + networkStorageCount, + openTicketCount, + networkVlanCount, + subnetCount, + userCount, + virtualGuestCount + ] + """ + return self.client.call('Account', 'getObject', mask=mask) + + def get_upcoming_events(self): + mask = "mask[id, subject, startDate, endDate, statusCode, acknowledgedFlag, impactedResourceCount, updateCount]" + _filter = { + 'endDate': { + 'operation': '> sysdate' + }, + 'startDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['ASC'] + }] + } + } + return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) \ No newline at end of file diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5c68f7c49..5a96c2267 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -288,3 +288,10 @@ def clean_string(string): return '' else: return " ".join(string.split()) +def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H:%M'): + + clean = datetime.datetime.strptime(sltime, in_format) + return clean.strftime(out_format) + + + From 3acc5b92bea430db30b83d70471b0bc4f20f1464 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Mon, 11 Mar 2019 18:13:50 -0400 Subject: [PATCH 0030/1582] 1101 handle and raise another exception message when oftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas occurs --- SoftLayer/managers/user.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 7031551c9..3c62d9112 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -243,7 +243,16 @@ def create_user(self, user_object, password): :param dictionary user_object: https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/ """ LOGGER.warning("Creating User %s", user_object['username']) - return self.user_service.createObject(user_object, password, None) + + try: + return self.user_service.createObject(user_object, password, None) + except exceptions.SoftLayerAPIError as err: + if err.faultCode != "SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas": + raise + else: + raise exceptions.SoftLayerError("Your request for a new user was received, but it needs to be " + "processed by the Platform Services API first. Barring any errors on " + "the Platform Services side, your new user should be created shortly.") def edit_user(self, user_id, user_object): """Blindly sends user_object to SoftLayer_User_Customer::editObject From e2694dfd5c4cde0b5007d485b74eb0fdf6c4bf43 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 12 Mar 2019 11:45:20 -0400 Subject: [PATCH 0031/1582] Upgrade file storage endurance iops. --- SoftLayer/managers/storage_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 07f19bd73..7cce7671b 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -241,7 +241,7 @@ def find_saas_endurance_space_price(package, size, tier_level): key_name = 'STORAGE_SPACE_FOR_{0}_IOPS_PER_GB'.format(tier_level) key_name = key_name.replace(".", "_") for item in package['items']: - if item['keyName'] != key_name: + if key_name not in item['keyName']: continue if 'capacityMinimum' not in item or 'capacityMaximum' not in item: From bcac437e99703d38d6ba5ccb6d988084237a46ed Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 12 Mar 2019 15:31:20 -0400 Subject: [PATCH 0032/1582] 1101 unit test --- SoftLayer/managers/user.py | 7 +++---- tests/managers/user_tests.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 3c62d9112..82cf62cd2 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -246,13 +246,12 @@ def create_user(self, user_object, password): try: return self.user_service.createObject(user_object, password, None) - except exceptions.SoftLayerAPIError as err: - if err.faultCode != "SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas": - raise - else: + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == "SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas": raise exceptions.SoftLayerError("Your request for a new user was received, but it needs to be " "processed by the Platform Services API first. Barring any errors on " "the Platform Services side, your new user should be created shortly.") + raise def edit_user(self, user_id, user_object): """Blindly sends user_object to SoftLayer_User_Customer::editObject diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index ddb8322f1..66443de04 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -191,3 +191,33 @@ def test_get_current_user_mask(self): result = self.manager.get_current_user(objectmask="mask[id]") self.assert_called_with('SoftLayer_Account', 'getCurrentUser', mask="mask[id]") self.assertEqual(result['id'], 12345) + + def test_create_user_handle_paas_exception(self): + user_template = {"username": "foobar", "email": "foobar@example.com"} + + self.manager.user_service = mock.Mock() + + # FaultCode IS NOT SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas + any_error = exceptions.SoftLayerAPIError("SoftLayer_Exception_User_Customer", + "This exception indicates an error") + + self.manager.user_service.createObject.side_effect = any_error + + try: + self.manager.create_user(user_template, "Pass@123") + except exceptions.SoftLayerAPIError as ex: + self.assertEqual(ex.faultCode, "SoftLayer_Exception_User_Customer") + self.assertEqual(ex.faultString, "This exception indicates an error") + + # FaultCode is SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas + paas_error = exceptions.SoftLayerAPIError("SoftLayer_Exception_User_Customer_DelegateIamIdInvitationToPaas", + "This exception does NOT indicate an error") + + self.manager.user_service.createObject.side_effect = paas_error + + try: + self.manager.create_user(user_template, "Pass@123") + except exceptions.SoftLayerError as ex: + self.assertEqual(ex.args[0], "Your request for a new user was received, but it needs to be processed by " + "the Platform Services API first. Barring any errors on the Platform Services " + "side, your new user should be created shortly.") From dd1fe43edb1933c6ff5aa7c45ec23ce92385255a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 14 Mar 2019 17:35:18 -0500 Subject: [PATCH 0033/1582] #1002 invoice list/details, event list/details --- SoftLayer/CLI/account/event_detail.py | 67 +++++++++++++++++++ .../CLI/account/{maintenance.py => events.py} | 14 ++-- SoftLayer/CLI/account/invoice_detail.py | 50 ++++++++++++-- SoftLayer/CLI/account/invoice_list.py | 21 ------ SoftLayer/CLI/account/invoices.py | 49 ++++++++++++++ SoftLayer/CLI/routes.py | 5 +- SoftLayer/managers/account.py | 51 +++++++++++++- 7 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 SoftLayer/CLI/account/event_detail.py rename SoftLayer/CLI/account/{maintenance.py => events.py} (76%) delete mode 100644 SoftLayer/CLI/account/invoice_list.py create mode 100644 SoftLayer/CLI/account/invoices.py diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py new file mode 100644 index 000000000..e38a19cdc --- /dev/null +++ b/SoftLayer/CLI/account/event_detail.py @@ -0,0 +1,67 @@ +"""Details of a specific event, and ability to acknowledge event.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils + +@click.command() +@click.argument('identifier') +@click.option('--ack', is_flag=True, default=False, + help="Acknowledge Event. Doing so will turn off the popup in the control portal") +@environment.pass_env +def cli(env, identifier, ack): + """Details of a specific event, and ability to acknowledge event.""" + + # Print a list of all on going maintenance + manager = AccountManager(env.client) + event = manager.get_event(identifier) + + if ack: + result = manager.ack_event(identifier) + + env.fout(basic_event_table(event)) + env.fout(impacted_table(event)) + env.fout(update_table(event)) + +def basic_event_table(event): + table = formatting.Table(["Id", "Status", "Type", "Start", "End"], title=event.get('subject')) + + table.add_row([ + event.get('id'), + utils.lookup(event, 'statusCode', 'name'), + utils.lookup(event, 'notificationOccurrenceEventType', 'keyName'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('endDate')) + ]) + + return table + +def impacted_table(event): + table = formatting.Table([ + "Type", "Id", "hostname", "privateIp", "Label" + ]) + for item in event.get('impactedResources', []): + table.add_row([ + item.get('resourceType'), + item.get('resourceTableId'), + item.get('hostname'), + item.get('privateIp'), + item.get('filterLabel') + ]) + return table + +def update_table(event): + update_number = 0 + for update in event.get('updates', []): + 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("\n".join(text.splitlines())) diff --git a/SoftLayer/CLI/account/maintenance.py b/SoftLayer/CLI/account/events.py similarity index 76% rename from SoftLayer/CLI/account/maintenance.py rename to SoftLayer/CLI/account/events.py index e9532b041..eb256de2c 100644 --- a/SoftLayer/CLI/account/maintenance.py +++ b/SoftLayer/CLI/account/events.py @@ -1,4 +1,4 @@ -"""Account Maintance manager""" +"""Summary and acknowledgement of upcoming and ongoing maintenance events""" # :license: MIT, see LICENSE for more details. from pprint import pprint as pp import click @@ -11,14 +11,20 @@ from SoftLayer import utils @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") @environment.pass_env -def cli(env): - """Summary and acknowledgement of upcoming and ongoing maintenance""" +def cli(env, ack_all): + """Summary and acknowledgement of upcoming and ongoing maintenance events""" # Print a list of all on going maintenance manager = AccountManager(env.client) events = manager.get_upcoming_events() + + if ack_all: + for event in events: + result = manager.ack_event(event['id']) + event['acknowledgedFlag'] = result env.fout(event_table(events)) # pp(events) diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index 695d68e66..79925fbd2 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -7,14 +7,54 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions 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() - +@click.argument('identifier') +@click.option('--details', is_flag=True, default=False, show_default=True, + help="Shows a very detailed list of charges") @environment.pass_env -def cli(env): +def cli(env, identifier, details): """Invoices and all that mess""" - # Print a detail of upcoming invoice, or specified invoice + manager = AccountManager(env.client) + top_items = manager.get_billing_items(identifier) + + title = "Invoice %s" % identifier + table = formatting.Table(["Item Id", "category", "description", "Single", "Monthly", "Create Date", "Location"], title=title) + table.align['category'] = 'l' + table.align['description'] = 'l' + for item in top_items: + fqdn = "%s.%s" % (item.get('hostName', ''), item.get('domainName', '')) + # category id=2046, ram_usage doesn't have a name... + category = utils.lookup(item, 'category', 'name') or item.get('categoryCode') + description = nice_string(item.get('description')) + if fqdn != '.': + description = "%s (%s)" % (item.get('description'), fqdn) + table.add_row([ + item.get('id'), + category, + nice_string(description), + "$%.2f" % float(item.get('oneTimeAfterTaxAmount')), + "$%.2f" % float(item.get('recurringAfterTaxAmount')), + utils.clean_time(item.get('createDate'), out_format="%Y-%m-%d"), + utils.lookup(item, 'location', 'name') + ]) + if details: + for child in item.get('children',[]): + table.add_row([ + '>>>', + utils.lookup(child, 'category', 'name'), + nice_string(child.get('description')), + "$%.2f" % float(child.get('oneTimeAfterTaxAmount')), + "$%.2f" % float(child.get('recurringAfterTaxAmount')), + '---', + '---' + ]) + + env.fout(table) - # export to pdf/excel \ No newline at end of file +def nice_string(ugly_string, limit=100): + return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string \ No newline at end of file diff --git a/SoftLayer/CLI/account/invoice_list.py b/SoftLayer/CLI/account/invoice_list.py deleted file mode 100644 index 9427b4f26..000000000 --- a/SoftLayer/CLI/account/invoice_list.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Invoice listing""" -# :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 - - -@click.command() - -@environment.pass_env -def cli(env): - """Invoices and all that mess""" - - # List invoices - - # invoice id, total recurring, total one time, total other, summary of what was ordered - # 123, 5$, 0$, 0$, 1 hardware, 2 vsi, 1 storage, 1 vlan \ No newline at end of file diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py new file mode 100644 index 000000000..cb9c48225 --- /dev/null +++ b/SoftLayer/CLI/account/invoices.py @@ -0,0 +1,49 @@ +"""Invoice listing""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils +from pprint import pprint as pp + + +@click.command() +@click.option('--limit', default=50, show_default=True, + help="How many invoices to get back. ALL for EVERY invoice on your account") +@click.option('--closed', is_flag=True, default=False, show_default=True, + help="Include invoices with a CLOSED status.") +@click.option('--all', 'get_all', is_flag=True, default=False, show_default=True, + help="Return ALL invoices. There may be a lot of these.") +@environment.pass_env +def cli(env, limit, closed=False, get_all=False): + """Invoices and all that mess""" + + # List invoices + + manager = AccountManager(env.client) + invoices = manager.get_invoices(limit, closed, get_all) + + table = formatting.Table([ + "Id", "Created", "Type", "Status", "Starting Balance", "Ending Balance", "Invoice Amount", "Items" + ]) + table.align['Starting Balance'] = 'l' + table.align['Ending Balance'] = 'l' + table.align['Invoice Amount'] = 'l' + table.align['Items'] = 'l' + for invoice in invoices: + table.add_row([ + invoice.get('id'), + utils.clean_time(invoice.get('createDate'), out_format="%Y-%m-%d"), + invoice.get('typeCode'), + invoice.get('statusCode'), + invoice.get('startingBalance'), + invoice.get('endingBalance'), + invoice.get('invoiceTotalAmount'), + invoice.get('itemCount') + ]) + env.fout(table) \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index bb6bbad78..c2feab178 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -13,8 +13,9 @@ ('account', 'SoftLayer.CLI.account'), ('account:invoice-detail', 'SoftLayer.CLI.account.invoice_detail:cli'), - ('account:invoice-list', 'SoftLayer.CLI.account.invoice_list:cli'), - ('account:maintenance', 'SoftLayer.CLI.account.maintenance:cli'), + ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), + ('account:events', 'SoftLayer.CLI.account.events:cli'), + ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), ('virtual', 'SoftLayer.CLI.virt'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 8d1ecb176..505da6363 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -58,4 +58,53 @@ def get_upcoming_events(self): }] } } - return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) \ No newline at end of file + return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + + def ack_event(self, event_id): + return self.client.call('Notification_Occurrence_Event', 'acknowledgeNotification', id=event_id) + + def get_event(self, event_id): + mask = """mask[ + acknowledgedFlag, + attachments, + impactedResources, + statusCode, + updates, + notificationOccurrenceEventType] + """ + return self.client.call('Notification_Occurrence_Event', 'getObject', id=event_id, mask=mask) + + def get_invoices(self, limit, closed=False, get_all=False): + mask = "mask[invoiceTotalAmount, itemCount]" + _filter = { + 'invoices': { + 'createDate' : { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + }, + 'statusCode': {'operation': 'OPEN'}, + } + } + if closed: + del _filter['invoices']['statusCode'] + + return self.client.call('Account', 'getInvoices', mask=mask, filter=_filter, iter=get_all, limit=limit) + + def get_billing_items(self, identifier): + + mask = """mask[ + id, description, hostName, domainName, oneTimeAfterTaxAmount, recurringAfterTaxAmount, createDate, + categoryCode, + category[name], + location[name], + children[id, category[name], description, oneTimeAfterTaxAmount, recurringAfterTaxAmount] + ]""" + return self.client.call('Billing_Invoice', 'getInvoiceTopLevelItems', id=identifier, mask=mask, iter=True, limit=100) + + def get_child_items(self, identifier): + mask = "mask[id, description, oneTimeAfterTaxAmount, recurringAfterTaxAmount, category[name], location[name]]" + return self.client.call('Billing_Invoice_Item', 'getChildren', id=identifier, mask=mask) From f634e8a5b3f2d14077d64a50cfba5238102fda3f Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 18 Mar 2019 15:35:02 -0500 Subject: [PATCH 0034/1582] basic unit tests --- ...SoftLayer_Notification_Occurrence_Event.py | 22 +++++++++++++++++++ tests/CLI/modules/account_tests.py | 21 ++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py create mode 100644 tests/CLI/modules/account_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py new file mode 100644 index 000000000..7fc425750 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py @@ -0,0 +1,22 @@ +getObject = { + 'endDate': '2019-03-18T17:00:00-06:00', + 'id': 174093, + 'lastImpactedUserCount': 417756, + 'modifyDate': '2019-03-12T15:32:48-06:00', + 'recoveryTime': None, + 'startDate': '2019-03-18T16:00:00-06:00', + 'subject': 'Public Website Maintenance', + 'summary': 'Blah Blah Blah', + 'systemTicketId': 76057381, + 'acknowledgedFlag': False, + 'attachments': [], + 'impactedResources': [], + 'notificationOccurrenceEventType': {'keyName': 'PLANNED'}, + 'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'}, + 'updates': [{ + 'contents': 'More Blah Blah', + 'createDate': '2019-03-12T13:07:22-06:00', + 'endDate': None, 'startDate': '2019-03-12T13:07:22-06:00' + } + ] +} diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py new file mode 100644 index 000000000..272ababbb --- /dev/null +++ b/tests/CLI/modules/account_tests.py @@ -0,0 +1,21 @@ +""" + SoftLayer.tests.CLI.modules.account_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +import json +import sys + +import mock +import testtools + +from SoftLayer import testing + + +class AccountCLITests(testing.TestCase): + + def test_event_detail(self): + result = self.run_command(['account', 'event-detail', '1234']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Notification_Occurrence_Event', 'getObject', identifier='1234') \ No newline at end of file From 85fd40d32204be663746f9b373b681b768b09d82 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 20 Mar 2019 16:33:27 -0400 Subject: [PATCH 0035/1582] Fix order place quantity option. --- SoftLayer/CLI/order/place.py | 7 ++-- SoftLayer/managers/ordering.py | 12 ++++--- tests/CLI/modules/order_tests.py | 21 ++++++++++++ tests/managers/ordering_tests.py | 57 ++++++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 3fe13caac..311c49a0a 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -23,6 +23,9 @@ @click.option('--verify', is_flag=True, help="Flag denoting whether or not to only verify the order, not place it") +@click.option('--quantity', + type=int, + help="The quantity of the item being ordered ") @click.option('--billing', type=click.Choice(['hourly', 'monthly']), default='hourly', @@ -35,7 +38,7 @@ @click.argument('order_items', nargs=-1) @environment.pass_env def cli(env, package_keyname, location, preset, verify, billing, complex_type, - extras, order_items): + quantity, extras, order_items): """Place or verify an order. This CLI command is used for placing/verifying an order of the specified package in @@ -84,7 +87,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, args = (package_keyname, location, order_items) kwargs = {'preset_keyname': preset, 'extras': extras, - 'quantity': 1, + 'quantity': quantity, 'complex_type': complex_type, 'hourly': bool(billing == 'hourly')} diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index fbc56b654..a7b53ae7c 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -415,7 +415,7 @@ def get_item_prices(self, package_id): return prices def verify_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=1): + hourly=True, preset_keyname=None, extras=None, quantity=None): """Verifies an order with the given package and prices. This function takes in parameters needed for an order and verifies the order @@ -446,7 +446,7 @@ def verify_order(self, package_keyname, location, item_keynames, complex_type=No return self.order_svc.verifyOrder(order) def place_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=1): + hourly=True, preset_keyname=None, extras=None, quantity=None): """Places an order with the given package and prices. This function takes in parameters needed for an order and places the order. @@ -509,7 +509,7 @@ def place_quote(self, package_keyname, location, item_keynames, complex_type=Non return self.order_svc.placeQuote(order) def generate_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=1): + hourly=True, preset_keyname=None, extras=None, quantity=None): """Generates an order with the given package and prices. This function takes in parameters needed for an order and generates an order @@ -546,7 +546,6 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type= order.update(extras) order['packageId'] = package['id'] order['location'] = self.get_location_id(location) - order['quantity'] = quantity order['useHourlyPricing'] = hourly preset_core = None @@ -562,6 +561,11 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type= raise exceptions.SoftLayerError("A complex type must be specified with the order") order['complexType'] = complex_type + if not quantity: + order['quantity'] = 1 + else: + order['quantity'] = quantity + price_ids = self.get_price_id_list(package_keyname, item_keynames, preset_core) order['prices'] = [{'id': price_id} for price_id in price_ids] diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 854690ac6..45876a704 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -104,6 +104,27 @@ def test_place(self): 'status': 'APPROVED'}, json.loads(result.output)) + def test_place_with_quantity(self): + order_date = '2017-04-04 07:39:20' + order = {'orderId': 1234, 'orderDate': order_date, 'placedOrder': {'status': 'APPROVED'}} + verify_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + items_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + verify_mock.return_value = self._get_verified_order_return() + place_mock.return_value = order + items_mock.return_value = self._get_order_items() + + result = self.run_command(['-y', 'order', 'place', '--quantity=2','package', 'DALLAS13', 'ITEM1', + '--complex-type', 'SoftLayer_Container_Product_Order_Thing']) + + 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)) + def test_place_extras_parameter_fail(self): result = self.run_command(['-y', 'order', 'place', 'package', 'DALLAS13', 'ITEM1', '--extras', '{"device":[']) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index b5d2aaa48..66eb2f405 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -370,6 +370,36 @@ def test_generate_order_with_preset(self): mock_get_ids.assert_called_once_with(pkg, items, 8) self.assertEqual(expected_order, order) + def test_generate_order_with_quantity(self): + pkg = 'PACKAGE_KEYNAME' + quantity = 2 + items = ['ITEM1', 'ITEM2'] + extras = {"hardware": [{"hostname": "test01", "domain": "example.com"}, + {"hostname": "test02", "domain": "example.com"}]} + complex_type = 'My_Type' + expected_order = {'orderContainers': [ + {'complexType': 'My_Type', + 'hardware': [{'domain': 'example.com', + 'hostname': 'test01'}, + {'domain': 'example.com', + 'hostname': 'test02'}], + 'location': 1854895, + 'packageId': 1234, + 'prices': [{'id': 1111}, {'id': 2222}], + 'quantity': 2, + 'useHourlyPricing': True} + ]} + + mock_pkg, mock_preset, mock_get_ids = self._patch_for_generate() + + order = self.ordering.generate_order(pkg, 'DALLAS13', items, complex_type=complex_type, quantity=quantity, + extras=extras) + + mock_pkg.assert_called_once_with(pkg, mask='id') + mock_preset.assert_not_called() + mock_get_ids.assert_called_once_with(pkg, items, None) + self.assertEqual(expected_order, order) + def test_generate_order(self): pkg = 'PACKAGE_KEYNAME' items = ['ITEM1', 'ITEM2'] @@ -444,6 +474,33 @@ def test_place_order(self): extras=extras, quantity=quantity) self.assertEqual(ord_mock.return_value, order) + def test_place_order_with_quantity(self): + ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + ord_mock.return_value = {'id': 1234} + pkg = 'PACKAGE_KEYNAME' + location = 'DALLAS13' + items = ['ITEM1', 'ITEM2'] + hourly = True + preset_keyname = 'PRESET' + complex_type = 'Complex_Type' + extras = {"hardware": [{"hostname": "test01", "domain": "example.com"}, + {"hostname": "test02", "domain": "example.com"}]} + quantity = 2 + + with mock.patch.object(self.ordering, 'generate_order') as gen_mock: + gen_mock.return_value = {'order': {}} + + order = self.ordering.place_order(pkg, location, items, hourly=hourly, + preset_keyname=preset_keyname, + complex_type=complex_type, + extras=extras, quantity=quantity) + + gen_mock.assert_called_once_with(pkg, location, items, hourly=hourly, + preset_keyname=preset_keyname, + complex_type=complex_type, + extras=extras, quantity=quantity) + self.assertEqual(ord_mock.return_value, order) + def test_place_quote(self): ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote') ord_mock.return_value = {'id': 1234} From d82ba322450cbbb0d99d00417b48578a3807789b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 20 Mar 2019 16:36:55 -0400 Subject: [PATCH 0036/1582] Fix order place quantity option. --- SoftLayer/CLI/order/place.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 311c49a0a..eb86c40ad 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -25,7 +25,7 @@ help="Flag denoting whether or not to only verify the order, not place it") @click.option('--quantity', type=int, - help="The quantity of the item being ordered ") + help="The quantity of the item being ordered") @click.option('--billing', type=click.Choice(['hourly', 'monthly']), default='hourly', From ee8fe379b43411482c36b021b141ddb81d02a009 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 20 Mar 2019 17:29:45 -0400 Subject: [PATCH 0037/1582] Fix order place quantity option. --- 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 45876a704..02141e808 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -115,7 +115,7 @@ def test_place_with_quantity(self): place_mock.return_value = order items_mock.return_value = self._get_order_items() - result = self.run_command(['-y', 'order', 'place', '--quantity=2','package', 'DALLAS13', 'ITEM1', + result = self.run_command(['-y', 'order', 'place', '--quantity=2', 'package', 'DALLAS13', 'ITEM1', '--complex-type', 'SoftLayer_Container_Product_Order_Thing']) self.assert_no_fail(result) From 31c1dad9348984592f1e3262935255b4927b5fa7 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 21 Mar 2019 13:05:45 -0400 Subject: [PATCH 0038/1582] Refactor order place quantity option. --- SoftLayer/CLI/order/place.py | 1 + SoftLayer/managers/ordering.py | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index eb86c40ad..c6f6a129b 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -25,6 +25,7 @@ help="Flag denoting whether or not to only verify the order, not place it") @click.option('--quantity', type=int, + default=1, help="The quantity of the item being ordered") @click.option('--billing', type=click.Choice(['hourly', 'monthly']), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index a7b53ae7c..c82a7ab5d 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -415,7 +415,7 @@ def get_item_prices(self, package_id): return prices def verify_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=None): + hourly=True, preset_keyname=None, extras=None, quantity=1): """Verifies an order with the given package and prices. This function takes in parameters needed for an order and verifies the order @@ -446,7 +446,7 @@ def verify_order(self, package_keyname, location, item_keynames, complex_type=No return self.order_svc.verifyOrder(order) def place_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=None): + hourly=True, preset_keyname=None, extras=None, quantity=1): """Places an order with the given package and prices. This function takes in parameters needed for an order and places the order. @@ -509,7 +509,7 @@ def place_quote(self, package_keyname, location, item_keynames, complex_type=Non return self.order_svc.placeQuote(order) def generate_order(self, package_keyname, location, item_keynames, complex_type=None, - hourly=True, preset_keyname=None, extras=None, quantity=None): + hourly=True, preset_keyname=None, extras=None, quantity=1): """Generates an order with the given package and prices. This function takes in parameters needed for an order and generates an order @@ -545,6 +545,7 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type= # 'domain': 'softlayer.com'}]} order.update(extras) order['packageId'] = package['id'] + order['quantity'] = quantity order['location'] = self.get_location_id(location) order['useHourlyPricing'] = hourly @@ -561,11 +562,6 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type= raise exceptions.SoftLayerError("A complex type must be specified with the order") order['complexType'] = complex_type - if not quantity: - order['quantity'] = 1 - else: - order['quantity'] = quantity - price_ids = self.get_price_id_list(package_keyname, item_keynames, preset_core) order['prices'] = [{'id': price_id} for price_id in price_ids] From a7c8db4e72cf4a6adf116c5c719224086f44f53a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 17:04:08 -0500 Subject: [PATCH 0039/1582] unit tests for slcli portion --- SoftLayer/CLI/account/invoices.py | 5 +- SoftLayer/CLI/account/summary.py | 1 - SoftLayer/fixtures/SoftLayer_Account.py | 24 ++++++ .../fixtures/SoftLayer_Billing_Invoice.py | 21 ++++++ ...SoftLayer_Notification_Occurrence_Event.py | 6 +- SoftLayer/managers/account.py | 9 ++- tests/CLI/modules/account_tests.py | 74 ++++++++++++++++++- 7 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Billing_Invoice.py diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index cb9c48225..d15a09ffd 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -5,16 +5,13 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions 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() @click.option('--limit', default=50, show_default=True, - help="How many invoices to get back. ALL for EVERY invoice on your account") + help="How many invoices to get back.") @click.option('--closed', is_flag=True, default=False, show_default=True, help="Include invoices with a CLOSED status.") @click.option('--all', 'get_all', is_flag=True, default=False, show_default=True, diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py index 7cff82789..71f2b1c82 100644 --- a/SoftLayer/CLI/account/summary.py +++ b/SoftLayer/CLI/account/summary.py @@ -40,5 +40,4 @@ def get_snapshot_table(account): table.add_row(['Network Vlans', account.get('networkVlanCount', '-')]) table.add_row(['Subnets', account.get('subnetCount', '-')]) table.add_row(['Users', account.get('userCount', '-')]) - # table.add_row(['', account.get('', '-')]) return table diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index b4bafac92..2ff5203cc 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -662,3 +662,27 @@ "name": "SPREAD" } }] + +getInvoices = [ + { + 'id': 33816665, + 'modifyDate': '2019-03-04T00:17:42-06:00', + 'createDate': '2019-03-04T00:17:42-06:00', + 'startingBalance': '129251.73', + 'statusCode': 'OPEN', + 'typeCode': 'RECURRING', + 'itemCount': 3317, + 'invoiceTotalAmount': '6230.66' + }, + { + 'id': 12345667, + 'modifyDate': '2019-03-05T00:17:42-06:00', + 'createDate': '2019-03-04T00:17:42-06:00', + 'startingBalance': '129251.73', + 'statusCode': 'OPEN', + 'typeCode': 'RECURRING', + 'itemCount': 12, + 'invoiceTotalAmount': '6230.66', + 'endingBalance': '12345.55' + } +] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py new file mode 100644 index 000000000..432d417c2 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py @@ -0,0 +1,21 @@ +getInvoiceTopLevelItems = [ + { + 'categoryCode': 'sov_sec_ip_addresses_priv', + 'createDate': '2018-04-04T23:15:20-06:00', + 'description': '64 Portable Private IP Addresses', + 'id': 724951323, + 'oneTimeAfterTaxAmount': '0', + 'recurringAfterTaxAmount': '0', + 'category': {'name': 'Private (only) Secondary VLAN IP Addresses'}, + 'children': [ + { + 'id': 12345, + 'category': {'name': 'Fake Child Category'}, + 'description': 'Blah', + 'oneTimeAfterTaxAmount': 55.50, + 'recurringAfterTaxAmount': 0.10 + } + ], + 'location': {'name': 'fra02'} + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py index 7fc425750..61352a3f5 100644 --- a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py +++ b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py @@ -1,6 +1,6 @@ getObject = { 'endDate': '2019-03-18T17:00:00-06:00', - 'id': 174093, + 'id': 1234, 'lastImpactedUserCount': 417756, 'modifyDate': '2019-03-12T15:32:48-06:00', 'recoveryTime': None, @@ -20,3 +20,7 @@ } ] } + +getAllObjects = [getObject] + +acknowledgeNotification = True diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 505da6363..fcfbb0454 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -103,7 +103,14 @@ def get_billing_items(self, identifier): location[name], children[id, category[name], description, oneTimeAfterTaxAmount, recurringAfterTaxAmount] ]""" - return self.client.call('Billing_Invoice', 'getInvoiceTopLevelItems', id=identifier, mask=mask, iter=True, limit=100) + return self.client.call( + 'Billing_Invoice', + 'getInvoiceTopLevelItems', + id=identifier, + mask=mask, + iter=True, + limit=100 + ) def get_child_items(self, identifier): mask = "mask[id, description, oneTimeAfterTaxAmount, recurringAfterTaxAmount, category[name], location[name]]" diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 272ababbb..452fd244f 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -15,7 +15,79 @@ class AccountCLITests(testing.TestCase): + def set_up(self): + self.SLNOE = 'SoftLayer_Notification_Occurrence_Event' + + #### slcli account event-detail #### def test_event_detail(self): result = self.run_command(['account', 'event-detail', '1234']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Notification_Occurrence_Event', 'getObject', identifier='1234') \ No newline at end of file + self.assert_called_with(self.SLNOE, 'getObject', identifier='1234') + + def test_event_details_ack(self): + result = self.run_command(['account', 'event-detail', '1234', '--ack']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getObject', identifier='1234') + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier='1234') + + #### slcli account events #### + def test_events(self): + result = self.run_command(['account', 'events']) + self.assert_no_fail(result) + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_event_ack_all(self): + result = self.run_command(['account', 'events', '--ack-all']) + self.assert_called_with(self.SLNOE, 'getAllObjects') + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) + + + #### slcli account invoice-detail #### + def test_invoice_detail(self): + result = self.run_command(['account', 'invoice-detail', '1234']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') + + def test_invoice_detail(self): + result = self.run_command(['account', 'invoice-detail', '1234', '--details']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') + + #### slcli account invoices #### + def test_invoices(self): + result = self.run_command(['account', 'invoices']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) + + def test_invoices_limited(self): + result = self.run_command(['account', 'invoices', '--limit=10']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=10) + + def test_invoices_closed(self): + _filter = { + 'invoices': { + 'createDate' : { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + } + } + } + result = self.run_command(['account', 'invoices', '--closed']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50, filter=_filter) + + def test_invoices_all(self): + result = self.run_command(['account', 'invoices', '--all']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) + + #### slcli account summary #### + result = self.run_command(['account', 'summary']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject') + + From 1821bf8351071b7157b173ab806d4a8f848052e8 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 17:26:08 -0500 Subject: [PATCH 0040/1582] account manager tests --- SoftLayer/managers/account.py | 6 +--- tests/managers/account_tests.py | 56 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 tests/managers/account_tests.py diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index fcfbb0454..62785f437 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -75,7 +75,7 @@ def get_event(self, event_id): """ return self.client.call('Notification_Occurrence_Event', 'getObject', id=event_id, mask=mask) - def get_invoices(self, limit, closed=False, get_all=False): + def get_invoices(self, limit=50, closed=False, get_all=False): mask = "mask[invoiceTotalAmount, itemCount]" _filter = { 'invoices': { @@ -111,7 +111,3 @@ def get_billing_items(self, identifier): iter=True, limit=100 ) - - def get_child_items(self, identifier): - mask = "mask[id, description, oneTimeAfterTaxAmount, recurringAfterTaxAmount, category[name], location[name]]" - return self.client.call('Billing_Invoice_Item', 'getChildren', id=identifier, mask=mask) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py new file mode 100644 index 000000000..89a0f2917 --- /dev/null +++ b/tests/managers/account_tests.py @@ -0,0 +1,56 @@ +""" + SoftLayer.tests.managers.account_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" + +import mock +import SoftLayer +from SoftLayer import exceptions +from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import testing + +class AccountManagerTests(testing.TestCase): + + def set_up(self): + self.manager = AccountManager(self.client) + self.SLNOE = 'SoftLayer_Notification_Occurrence_Event' + + def test_get_summary(self): + self.manager.get_summary() + self.assert_called_with('SoftLayer_Account', 'getObject') + + def test_get_upcoming_events(self): + self.manager.get_upcoming_events() + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_ack_event(self): + self.manager.ack_event(12345) + self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=12345) + + def test_get_event(self): + self.manager.get_event(12345) + self.assert_called_with(self.SLNOE, 'getObject', identifier=12345) + + def test_get_invoices(self): + self.manager.get_invoices() + self.assert_called_with('SoftLayer_Account', 'getInvoices') + + def test_get_invoices_closed(self): + self.manager.get_invoices(closed=True) + _filter = { + 'invoices': { + 'createDate' : { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }] + } + } + } + self.assert_called_with('SoftLayer_Account', 'getInvoices', filter=_filter) + + def test_get_billing_items(self): + self.manager.get_billing_items(12345) + self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems') From 63f96170c8b10b8fad2e432d80b7ee036fce9d46 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 17:29:43 -0500 Subject: [PATCH 0041/1582] autopep8 fixes --- SoftLayer/CLI/account/event_detail.py | 6 +++- SoftLayer/CLI/account/events.py | 6 ++-- SoftLayer/CLI/account/invoice_detail.py | 9 ++++-- SoftLayer/CLI/account/invoices.py | 3 +- SoftLayer/CLI/account/summary.py | 2 +- SoftLayer/CLI/user/orders.py | 3 +- SoftLayer/fixtures/SoftLayer_Account.py | 4 +-- .../fixtures/SoftLayer_Billing_Invoice.py | 8 ++--- ...SoftLayer_Notification_Occurrence_Event.py | 32 +++++++++---------- SoftLayer/managers/account.py | 3 +- SoftLayer/utils.py | 5 ++- tests/CLI/modules/account_tests.py | 6 ++-- tests/CLI/modules/event_log_tests.py | 26 +++++++-------- tests/CLI/modules/securitygroup_tests.py | 4 +-- tests/CLI/modules/vs/vs_create_tests.py | 12 +++---- tests/managers/account_tests.py | 3 +- 16 files changed, 69 insertions(+), 63 deletions(-) diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index e38a19cdc..ffcd9ecb2 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -10,6 +10,7 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils + @click.command() @click.argument('identifier') @click.option('--ack', is_flag=True, default=False, @@ -18,7 +19,7 @@ def cli(env, identifier, ack): """Details of a specific event, and ability to acknowledge event.""" - # Print a list of all on going maintenance + # Print a list of all on going maintenance manager = AccountManager(env.client) event = manager.get_event(identifier) @@ -29,6 +30,7 @@ def cli(env, identifier, ack): env.fout(impacted_table(event)) env.fout(update_table(event)) + def basic_event_table(event): table = formatting.Table(["Id", "Status", "Type", "Start", "End"], title=event.get('subject')) @@ -42,6 +44,7 @@ def basic_event_table(event): return table + def impacted_table(event): table = formatting.Table([ "Type", "Id", "hostname", "privateIp", "Label" @@ -56,6 +59,7 @@ def impacted_table(event): ]) return table + def update_table(event): update_number = 0 for update in event.get('updates', []): diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index eb256de2c..e1c90cd14 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -10,6 +10,7 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils + @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") @@ -17,7 +18,7 @@ def cli(env, ack_all): """Summary and acknowledgement of upcoming and ongoing maintenance events""" - # Print a list of all on going maintenance + # Print a list of all on going maintenance manager = AccountManager(env.client) events = manager.get_upcoming_events() @@ -30,6 +31,7 @@ def cli(env, ack_all): # Allow ack all, or ack specific maintenance + def event_table(events): table = formatting.Table([ "Id", @@ -54,4 +56,4 @@ def event_table(events): event.get('updateCount'), event.get('impactedResourceCount') ]) - return table \ No newline at end of file + return table diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index 79925fbd2..44e5d7f47 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -11,6 +11,7 @@ from SoftLayer import utils from pprint import pprint as pp + @click.command() @click.argument('identifier') @click.option('--details', is_flag=True, default=False, show_default=True, @@ -23,7 +24,8 @@ def cli(env, identifier, details): top_items = manager.get_billing_items(identifier) title = "Invoice %s" % identifier - table = formatting.Table(["Item Id", "category", "description", "Single", "Monthly", "Create Date", "Location"], title=title) + table = formatting.Table(["Item Id", "category", "description", "Single", + "Monthly", "Create Date", "Location"], title=title) table.align['category'] = 'l' table.align['description'] = 'l' for item in top_items: @@ -43,7 +45,7 @@ def cli(env, identifier, details): utils.lookup(item, 'location', 'name') ]) if details: - for child in item.get('children',[]): + for child in item.get('children', []): table.add_row([ '>>>', utils.lookup(child, 'category', 'name'), @@ -56,5 +58,6 @@ def cli(env, identifier, details): env.fout(table) + def nice_string(ugly_string, limit=100): - return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string \ No newline at end of file + return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index d15a09ffd..f28098b9e 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -9,6 +9,7 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils + @click.command() @click.option('--limit', default=50, show_default=True, help="How many invoices to get back.") @@ -43,4 +44,4 @@ def cli(env, limit, closed=False, get_all=False): invoice.get('invoiceTotalAmount'), invoice.get('itemCount') ]) - env.fout(table) \ No newline at end of file + env.fout(table) diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py index 71f2b1c82..90dfcdf3b 100644 --- a/SoftLayer/CLI/account/summary.py +++ b/SoftLayer/CLI/account/summary.py @@ -11,8 +11,8 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils -@click.command() +@click.command() @environment.pass_env def cli(env): """Prints some various bits of information about an account""" diff --git a/SoftLayer/CLI/user/orders.py b/SoftLayer/CLI/user/orders.py index 12688f397..55ca4516d 100644 --- a/SoftLayer/CLI/user/orders.py +++ b/SoftLayer/CLI/user/orders.py @@ -10,11 +10,10 @@ @click.command() - @environment.pass_env def cli(env): """Lists each user and the servers they ordered""" # Table = [user name, fqdn, cost] # maybe print ordered storage / network bits - # if given a single user id, just print detailed info from that user \ No newline at end of file + # if given a single user id, just print detailed info from that user diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 2ff5203cc..bd5a45d0e 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -673,7 +673,7 @@ 'typeCode': 'RECURRING', 'itemCount': 3317, 'invoiceTotalAmount': '6230.66' - }, + }, { 'id': 12345667, 'modifyDate': '2019-03-05T00:17:42-06:00', @@ -685,4 +685,4 @@ 'invoiceTotalAmount': '6230.66', 'endingBalance': '12345.55' } -] \ No newline at end of file +] diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py index 432d417c2..5c1c10f9a 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py @@ -4,9 +4,9 @@ 'createDate': '2018-04-04T23:15:20-06:00', 'description': '64 Portable Private IP Addresses', 'id': 724951323, - 'oneTimeAfterTaxAmount': '0', - 'recurringAfterTaxAmount': '0', - 'category': {'name': 'Private (only) Secondary VLAN IP Addresses'}, + 'oneTimeAfterTaxAmount': '0', + 'recurringAfterTaxAmount': '0', + 'category': {'name': 'Private (only) Secondary VLAN IP Addresses'}, 'children': [ { 'id': 12345, @@ -15,7 +15,7 @@ 'oneTimeAfterTaxAmount': 55.50, 'recurringAfterTaxAmount': 0.10 } - ], + ], 'location': {'name': 'fra02'} } ] diff --git a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py index 61352a3f5..b57177cf4 100644 --- a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py +++ b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py @@ -1,23 +1,23 @@ getObject = { - 'endDate': '2019-03-18T17:00:00-06:00', - 'id': 1234, - 'lastImpactedUserCount': 417756, - 'modifyDate': '2019-03-12T15:32:48-06:00', - 'recoveryTime': None, - 'startDate': '2019-03-18T16:00:00-06:00', - 'subject': 'Public Website Maintenance', - 'summary': 'Blah Blah Blah', - 'systemTicketId': 76057381, + 'endDate': '2019-03-18T17:00:00-06:00', + 'id': 1234, + 'lastImpactedUserCount': 417756, + 'modifyDate': '2019-03-12T15:32:48-06:00', + 'recoveryTime': None, + 'startDate': '2019-03-18T16:00:00-06:00', + 'subject': 'Public Website Maintenance', + 'summary': 'Blah Blah Blah', + 'systemTicketId': 76057381, 'acknowledgedFlag': False, - 'attachments': [], - 'impactedResources': [], - 'notificationOccurrenceEventType': {'keyName': 'PLANNED'}, - 'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'}, + 'attachments': [], + 'impactedResources': [], + 'notificationOccurrenceEventType': {'keyName': 'PLANNED'}, + 'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'}, 'updates': [{ - 'contents': 'More Blah Blah', - 'createDate': '2019-03-12T13:07:22-06:00', + 'contents': 'More Blah Blah', + 'createDate': '2019-03-12T13:07:22-06:00', 'endDate': None, 'startDate': '2019-03-12T13:07:22-06:00' - } + } ] } diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 62785f437..29411f9fa 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -60,7 +60,6 @@ def get_upcoming_events(self): } return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) - def ack_event(self, event_id): return self.client.call('Notification_Occurrence_Event', 'acknowledgeNotification', id=event_id) @@ -79,7 +78,7 @@ def get_invoices(self, limit=50, closed=False, get_all=False): mask = "mask[invoiceTotalAmount, itemCount]" _filter = { 'invoices': { - 'createDate' : { + 'createDate': { 'operation': 'orderBy', 'options': [{ 'name': 'sort', diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5a96c2267..82e9fcedf 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -288,10 +288,9 @@ def clean_string(string): return '' else: return " ".join(string.split()) + + def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H:%M'): clean = datetime.datetime.strptime(sltime, in_format) return clean.strftime(out_format) - - - diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 452fd244f..dd5c7b45f 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -41,8 +41,8 @@ def test_event_ack_all(self): self.assert_called_with(self.SLNOE, 'getAllObjects') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) - #### slcli account invoice-detail #### + def test_invoice_detail(self): result = self.run_command(['account', 'invoice-detail', '1234']) self.assert_no_fail(result) @@ -67,7 +67,7 @@ def test_invoices_limited(self): def test_invoices_closed(self): _filter = { 'invoices': { - 'createDate' : { + 'createDate': { 'operation': 'orderBy', 'options': [{ 'name': 'sort', @@ -89,5 +89,3 @@ def test_invoices_all(self): result = self.run_command(['account', 'summary']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject') - - diff --git a/tests/CLI/modules/event_log_tests.py b/tests/CLI/modules/event_log_tests.py index a7ff0dcb1..b36ff8530 100644 --- a/tests/CLI/modules/event_log_tests.py +++ b/tests/CLI/modules/event_log_tests.py @@ -36,7 +36,7 @@ def test_get_event_log_with_metadata(self): '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -53,7 +53,7 @@ def test_get_event_log_with_metadata(self): '"requestId":"96c9b47b9e102d2e1d81fba",' '"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -70,7 +70,7 @@ def test_get_event_log_with_metadata(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -86,7 +86,7 @@ def test_get_event_log_with_metadata(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), + ), indent=4, sort_keys=True ) @@ -103,7 +103,7 @@ def test_get_event_log_with_metadata(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -119,7 +119,7 @@ def test_get_event_log_with_metadata(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"4709e02ad42c83f80345904"}' - ), + ), indent=4, sort_keys=True ) @@ -216,7 +216,7 @@ def test_get_event_table(self): '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -233,7 +233,7 @@ def test_get_event_table(self): '"requestId":"96c9b47b9e102d2e1d81fba",' '"securityGroupId":"200",' '"securityGroupName":"test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -250,7 +250,7 @@ def test_get_event_table(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -266,7 +266,7 @@ def test_get_event_table(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), + ), indent=4, sort_keys=True ) @@ -283,7 +283,7 @@ def test_get_event_table(self): '"ethertype":"IPv4",' '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), + ), indent=4, sort_keys=True ) @@ -299,7 +299,7 @@ def test_get_event_table(self): '"networkComponentId":"100",' '"networkInterfaceType":"public",' '"requestId":"4709e02ad42c83f80345904"}' - ), + ), indent=4, sort_keys=True ) @@ -308,7 +308,7 @@ def test_get_event_table(self): for log in expected: table_fix.add_row([log['event'], log['object'], log['type'], log['date'], - log['username'], log['metadata'].strip("{}\n\t")]) + log['username'], log['metadata'].strip("{}\n\t")]) expected_output = formatting.format_output(table_fix) + '\n' result = self.run_command(args=['event-log', 'get', '--metadata'], fmt='table') diff --git a/tests/CLI/modules/securitygroup_tests.py b/tests/CLI/modules/securitygroup_tests.py index 4ce0cd564..b6801fcc8 100644 --- a/tests/CLI/modules/securitygroup_tests.py +++ b/tests/CLI/modules/securitygroup_tests.py @@ -310,7 +310,7 @@ def test_securitygroup_get_by_request_id(self, event_mock): '"requestId": "96c9b47b9e102d2e1d81fba",' '"securityGroupId": "200",' '"securityGroupName": "test_SG"}' - ), + ), indent=4, sort_keys=True ) @@ -329,7 +329,7 @@ def test_securitygroup_get_by_request_id(self, event_mock): '"remoteGroupId": null,' '"remoteIp": null,' '"ruleId": "800"}]}' - ), + ), indent=4, sort_keys=True ) diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 5075d225e..91946471a 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -604,12 +604,12 @@ def test_create_with_userdata(self, confirm_mock): '--userdata', 'This is my user data ok']) self.assert_no_fail(result) expected_guest = [ - { - 'domain': 'test.local', - 'hostname': 'test', - 'userData': [{'value': 'This is my user data ok'}] - } - ] + { + 'domain': 'test.local', + 'hostname': 'test', + 'userData': [{'value': 'This is my user data ok'}] + } + ] # Returns a list of API calls that hit SL_Product_Order::placeOrder api_call = self.calls('SoftLayer_Product_Order', 'placeOrder') # Doing this because the placeOrder args are huge and mostly not needed to test diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 89a0f2917..38ca0d9f4 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -10,6 +10,7 @@ from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import testing + class AccountManagerTests(testing.TestCase): def set_up(self): @@ -40,7 +41,7 @@ def test_get_invoices_closed(self): self.manager.get_invoices(closed=True) _filter = { 'invoices': { - 'createDate' : { + 'createDate': { 'operation': 'orderBy', 'options': [{ 'name': 'sort', From b7b70f16ac0fbeb4d098a681148b03fdd75393bd Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 17:56:01 -0500 Subject: [PATCH 0042/1582] code cleanup and docs --- SoftLayer/CLI/account/event_detail.py | 3 +++ SoftLayer/CLI/account/events.py | 6 ----- SoftLayer/CLI/account/invoice_detail.py | 3 +-- SoftLayer/CLI/account/invoices.py | 2 -- SoftLayer/managers/account.py | 30 +++++++++++++++++++++++++ SoftLayer/utils.py | 5 +++++ docs/api/managers/account.rst | 5 +++++ docs/cli/account.rst | 23 +++++++++++++++++++ 8 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 docs/api/managers/account.rst create mode 100644 docs/cli/account.rst diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index ffcd9ecb2..7879da894 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -32,6 +32,7 @@ def cli(env, identifier, ack): def basic_event_table(event): + """Formats a basic event table""" table = formatting.Table(["Id", "Status", "Type", "Start", "End"], title=event.get('subject')) table.add_row([ @@ -46,6 +47,7 @@ def basic_event_table(event): def impacted_table(event): + """Formats a basic impacted resources table""" table = formatting.Table([ "Type", "Id", "hostname", "privateIp", "Label" ]) @@ -61,6 +63,7 @@ def impacted_table(event): def update_table(event): + """Formats a basic event update table""" update_number = 0 for update in event.get('updates', []): header = "======= Update #%s on %s =======" % (update_number, utils.clean_time(update.get('startDate'))) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index e1c90cd14..96a292b05 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -1,11 +1,9 @@ """Summary and acknowledgement of upcoming and ongoing maintenance events""" # :license: MIT, see LICENSE for more details. -from pprint import pprint as pp import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils @@ -18,7 +16,6 @@ def cli(env, ack_all): """Summary and acknowledgement of upcoming and ongoing maintenance events""" - # Print a list of all on going maintenance manager = AccountManager(env.client) events = manager.get_upcoming_events() @@ -27,9 +24,6 @@ def cli(env, ack_all): result = manager.ack_event(event['id']) event['acknowledgedFlag'] = result env.fout(event_table(events)) - # pp(events) - - # Allow ack all, or ack specific maintenance def event_table(events): diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index 44e5d7f47..e3964388c 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -5,11 +5,9 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions 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() @@ -60,4 +58,5 @@ def cli(env, identifier, details): def nice_string(ugly_string, limit=100): + """Format and trims strings""" return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index f28098b9e..13befba46 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -21,8 +21,6 @@ def cli(env, limit, closed=False, get_all=False): """Invoices and all that mess""" - # List invoices - manager = AccountManager(env.client) invoices = manager.get_invoices(limit, closed, get_all) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 29411f9fa..f13638bf7 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -27,6 +27,10 @@ def __init__(self, client): self.client = client def get_summary(self): + """Gets some basic account information + + :return: Account object + """ mask = """mask[ nextInvoiceTotalAmount, pendingInvoice[invoiceTotalAmount], @@ -45,6 +49,10 @@ def get_summary(self): return self.client.call('Account', 'getObject', mask=mask) def get_upcoming_events(self): + """Retreives a list of Notification_Occurrence_Events that have not ended yet + + :return: SoftLayer_Notification_Occurrence_Event + """ mask = "mask[id, subject, startDate, endDate, statusCode, acknowledgedFlag, impactedResourceCount, updateCount]" _filter = { 'endDate': { @@ -61,9 +69,19 @@ def get_upcoming_events(self): return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) def ack_event(self, event_id): + """Acknowledge an event. This mostly prevents it from appearing as a notification in the control portal. + + :param int event_id: Notification_Occurrence_Event ID you want to ack + :return: True on success, Exception otherwise. + """ return self.client.call('Notification_Occurrence_Event', 'acknowledgeNotification', id=event_id) def get_event(self, event_id): + """Gets details about a maintenance event + + :param int event_id: Notification_Occurrence_Event ID + :return: Notification_Occurrence_Event + """ mask = """mask[ acknowledgedFlag, attachments, @@ -75,6 +93,13 @@ def get_event(self, event_id): return self.client.call('Notification_Occurrence_Event', 'getObject', id=event_id, mask=mask) def get_invoices(self, limit=50, closed=False, get_all=False): + """Gets an accounts invoices. + + :param int limit: Number of invoices to get back in a single call. + :param bool closed: If True, will also get CLOSED invoices + :param bool get_all: If True, will paginate through invoices until all have been retrieved. + :return: Billing_Invoice + """ mask = "mask[invoiceTotalAmount, itemCount]" _filter = { 'invoices': { @@ -94,6 +119,11 @@ def get_invoices(self, limit=50, closed=False, get_all=False): return self.client.call('Account', 'getInvoices', mask=mask, filter=_filter, iter=get_all, limit=limit) def get_billing_items(self, identifier): + """Gets all topLevelBillingItems from a specific invoice + + :param int identifier: Invoice Id + :return: Billing_Invoice_Item + """ mask = """mask[ id, description, hostName, domainName, oneTimeAfterTaxAmount, recurringAfterTaxAmount, createDate, diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 82e9fcedf..90736560c 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -291,6 +291,11 @@ def clean_string(string): def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H:%M'): + """Easy way to format time strings + :param string sltime: A softlayer formatted time string + :param string in_format: Datetime format for strptime + :param string out_format: Datetime format for strftime + """ clean = datetime.datetime.strptime(sltime, in_format) return clean.strftime(out_format) diff --git a/docs/api/managers/account.rst b/docs/api/managers/account.rst new file mode 100644 index 000000000..25d76ed6a --- /dev/null +++ b/docs/api/managers/account.rst @@ -0,0 +1,5 @@ +.. _account: + +.. automodule:: SoftLayer.managers.account + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/account.rst b/docs/cli/account.rst new file mode 100644 index 000000000..9e368a6fb --- /dev/null +++ b/docs/cli/account.rst @@ -0,0 +1,23 @@ +.. _cli_account: + +Account Commands + +.. click:: SoftLayer.cli.account.summary:cli + :prog: account summary + :show-nested: + +.. click:: SoftLayer.cli.account.events:cli + :prog: account events + :show-nested: + +.. click:: SoftLayer.cli.account.event-detail:cli + :prog: account event-detail + :show-nested: + +.. click:: SoftLayer.cli.account.invoices:cli + :prog: account invoices + :show-nested: + +.. click:: SoftLayer.cli.account.invoice-detail:cli + :prog: account invoice-detail + :show-nested: \ No newline at end of file From fe4d7d282a36e73e68b384bb47346fbd06a6e91a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 18:30:17 -0500 Subject: [PATCH 0043/1582] tox cleanup --- SoftLayer/CLI/account/event_detail.py | 4 +--- SoftLayer/CLI/account/events.py | 2 +- SoftLayer/CLI/account/invoice_detail.py | 1 - SoftLayer/CLI/account/invoices.py | 1 - SoftLayer/CLI/account/summary.py | 4 ---- SoftLayer/CLI/user/orders.py | 19 ------------------ .../fixtures/SoftLayer_Billing_Invoice.py | 2 ++ ...SoftLayer_Notification_Occurrence_Event.py | 11 +++++++--- SoftLayer/managers/account.py | 11 +++++----- SoftLayer/utils.py | 4 ++-- docs/cli.rst | 4 ++-- docs/cli/account.rst | 12 ++++++----- tests/CLI/modules/account_tests.py | 20 ++++++++----------- tests/managers/account_tests.py | 3 --- 14 files changed, 36 insertions(+), 62 deletions(-) delete mode 100644 SoftLayer/CLI/user/orders.py diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index 7879da894..445093f15 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -3,9 +3,7 @@ import click -import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils @@ -24,7 +22,7 @@ def cli(env, identifier, ack): event = manager.get_event(identifier) if ack: - result = manager.ack_event(identifier) + manager.ack_event(identifier) env.fout(basic_event_table(event)) env.fout(impacted_table(event)) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 96a292b05..c1c9ff684 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -2,7 +2,6 @@ # :license: MIT, see LICENSE for more details. import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager @@ -27,6 +26,7 @@ def cli(env, ack_all): def event_table(events): + """Formats a table for events""" table = formatting.Table([ "Id", "Start Date", diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index e3964388c..dee77dae6 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -3,7 +3,6 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index 13befba46..3047e59d6 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -3,7 +3,6 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager diff --git a/SoftLayer/CLI/account/summary.py b/SoftLayer/CLI/account/summary.py index 90dfcdf3b..f1ae2b6be 100644 --- a/SoftLayer/CLI/account/summary.py +++ b/SoftLayer/CLI/account/summary.py @@ -1,12 +1,8 @@ """Account Summary page""" # :license: MIT, see LICENSE for more details. -from pprint import pprint as pp - import click -import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import utils diff --git a/SoftLayer/CLI/user/orders.py b/SoftLayer/CLI/user/orders.py deleted file mode 100644 index 55ca4516d..000000000 --- a/SoftLayer/CLI/user/orders.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Users order details""" -# :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 - - -@click.command() -@environment.pass_env -def cli(env): - """Lists each user and the servers they ordered""" - - # Table = [user name, fqdn, cost] - # maybe print ordered storage / network bits - # if given a single user id, just print detailed info from that user diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py index 5c1c10f9a..d4d89131c 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice.py @@ -6,6 +6,8 @@ 'id': 724951323, 'oneTimeAfterTaxAmount': '0', 'recurringAfterTaxAmount': '0', + 'hostName': 'bleg', + 'domainName': 'beh.com', 'category': {'name': 'Private (only) Secondary VLAN IP Addresses'}, 'children': [ { diff --git a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py index b57177cf4..7c6740431 100644 --- a/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py +++ b/SoftLayer/fixtures/SoftLayer_Notification_Occurrence_Event.py @@ -10,15 +10,20 @@ 'systemTicketId': 76057381, 'acknowledgedFlag': False, 'attachments': [], - 'impactedResources': [], + 'impactedResources': [{ + 'resourceType': 'Server', + 'resourceTableId': 12345, + 'hostname': 'test', + 'privateIp': '10.0.0.1', + 'filterLable': 'Server' + }], 'notificationOccurrenceEventType': {'keyName': 'PLANNED'}, 'statusCode': {'keyName': 'PUBLISHED', 'name': 'Published'}, 'updates': [{ 'contents': 'More Blah Blah', 'createDate': '2019-03-12T13:07:22-06:00', 'endDate': None, 'startDate': '2019-03-12T13:07:22-06:00' - } - ] + }] } getAllObjects = [getObject] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index f13638bf7..1f7d4871d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -7,7 +7,6 @@ """ import logging -import SoftLayer from SoftLayer import utils @@ -80,7 +79,7 @@ def get_event(self, event_id): """Gets details about a maintenance event :param int event_id: Notification_Occurrence_Event ID - :return: Notification_Occurrence_Event + :return: Notification_Occurrence_Event """ mask = """mask[ acknowledgedFlag, @@ -120,16 +119,16 @@ def get_invoices(self, limit=50, closed=False, get_all=False): def get_billing_items(self, identifier): """Gets all topLevelBillingItems from a specific invoice - + :param int identifier: Invoice Id :return: Billing_Invoice_Item """ mask = """mask[ id, description, hostName, domainName, oneTimeAfterTaxAmount, recurringAfterTaxAmount, createDate, - categoryCode, - category[name], - location[name], + categoryCode, + category[name], + location[name], children[id, category[name], description, oneTimeAfterTaxAmount, recurringAfterTaxAmount] ]""" return self.client.call( diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 90736560c..1e5c48d7c 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -142,9 +142,9 @@ def format_event_log_date(date_string, utc): utc = "+0000" iso_time_zone = utc[:3] + ':' + utc[3:] - clean_time = "{}.000000{}".format(dirty_time, iso_time_zone) + cleaned_time = "{}.000000{}".format(dirty_time, iso_time_zone) - return clean_time + return cleaned_time def event_log_filter_between_date(start, end, utc): diff --git a/docs/cli.rst b/docs/cli.rst index 6d8b18218..709741aa1 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -12,12 +12,12 @@ functionality not fully documented here. .. toctree:: :maxdepth: 2 - cli/ipsec + cli/account cli/vs cli/hardware cli/ordering cli/users - + cli/ipsec .. _config_setup: diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 9e368a6fb..9b3ad6954 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -1,23 +1,25 @@ .. _cli_account: Account Commands +================= -.. click:: SoftLayer.cli.account.summary:cli + +.. click:: SoftLayer.CLI.account.summary:cli :prog: account summary :show-nested: -.. click:: SoftLayer.cli.account.events:cli +.. click:: SoftLayer.CLI.account.events:cli :prog: account events :show-nested: -.. click:: SoftLayer.cli.account.event-detail:cli +.. click:: SoftLayer.CLI.account.event_detail:cli :prog: account event-detail :show-nested: -.. click:: SoftLayer.cli.account.invoices:cli +.. click:: SoftLayer.CLI.account.invoices:cli :prog: account invoices :show-nested: -.. click:: SoftLayer.cli.account.invoice-detail:cli +.. click:: SoftLayer.CLI.account.invoice_detail:cli :prog: account invoice-detail :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index dd5c7b45f..67575c53c 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -4,12 +4,6 @@ Tests for the user cli command """ -import json -import sys - -import mock -import testtools - from SoftLayer import testing @@ -18,7 +12,7 @@ class AccountCLITests(testing.TestCase): def set_up(self): self.SLNOE = 'SoftLayer_Notification_Occurrence_Event' - #### slcli account event-detail #### + # slcli account event-detail def test_event_detail(self): result = self.run_command(['account', 'event-detail', '1234']) self.assert_no_fail(result) @@ -30,7 +24,7 @@ def test_event_details_ack(self): self.assert_called_with(self.SLNOE, 'getObject', identifier='1234') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier='1234') - #### slcli account events #### + # slcli account events def test_events(self): result = self.run_command(['account', 'events']) self.assert_no_fail(result) @@ -38,22 +32,23 @@ def test_events(self): def test_event_ack_all(self): result = self.run_command(['account', 'events', '--ack-all']) + self.assert_no_fail(result) self.assert_called_with(self.SLNOE, 'getAllObjects') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) - #### slcli account invoice-detail #### + # slcli account invoice-detail def test_invoice_detail(self): result = self.run_command(['account', 'invoice-detail', '1234']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') - def test_invoice_detail(self): + def test_invoice_detail_details(self): result = self.run_command(['account', 'invoice-detail', '1234', '--details']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems', identifier='1234') - #### slcli account invoices #### + # slcli account invoices def test_invoices(self): result = self.run_command(['account', 'invoices']) self.assert_no_fail(result) @@ -85,7 +80,8 @@ def test_invoices_all(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) - #### slcli account summary #### + # slcli account summary + def test_account_summary(self): result = self.run_command(['account', 'summary']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 38ca0d9f4..7efc42acd 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -4,9 +4,6 @@ """ -import mock -import SoftLayer -from SoftLayer import exceptions from SoftLayer.managers.account import AccountManager as AccountManager from SoftLayer import testing From 1d4ca3f4912359cfc58ac4ef806a162878155ebc Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 21 Mar 2019 18:40:55 -0500 Subject: [PATCH 0044/1582] finishing tox unit tests --- SoftLayer/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 1e5c48d7c..918da2b09 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -297,5 +297,9 @@ def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H: :param string in_format: Datetime format for strptime :param string out_format: Datetime format for strftime """ - clean = datetime.datetime.strptime(sltime, in_format) - return clean.strftime(out_format) + try: + clean = datetime.datetime.strptime(sltime, in_format) + return clean.strftime(out_format) + # The %z option only exists with py3.6+ + except ValueError: + return sltime From 05b97231cedafc4f59b0558e58eb552b3260b52f Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Fri, 22 Mar 2019 14:49:46 -0400 Subject: [PATCH 0045/1582] 1117 Two PCIe items can be added at order time --- SoftLayer/managers/ordering.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index fbc56b654..ab7522098 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -340,7 +340,8 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): items = self.list_items(package_keyname, mask=mask) prices = [] - gpu_number = -1 + category_dict = {"gpu0": -1, "pcie_slot0": -1} + for item_keyname in item_keynames: try: # Need to find the item in the package that has a matching @@ -356,15 +357,17 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): # because that is the most generic price. verifyOrder/placeOrder # can take that ID and create the proper price for us in the location # in which the order is made - if matching_item['itemCategory']['categoryCode'] != "gpu0": + item_category = matching_item['itemCategory']['categoryCode'] + if item_category not in category_dict: price_id = self.get_item_price_id(core, matching_item['prices']) else: - # GPU items has two generic prices and they are added to the list - # according to the number of gpu items added in the order. - gpu_number += 1 + # GPU and PCIe items has two generic prices and they are added to the list + # according to the number of items in the order. + category_dict[item_category] += 1 + category_code = item_category[:-1] + str(category_dict[item_category]) price_id = [p['id'] for p in matching_item['prices'] if not p['locationGroupId'] - and p['categories'][0]['categoryCode'] == "gpu" + str(gpu_number)][0] + and p['categories'][0]['categoryCode'] == category_code][0] prices.append(price_id) From 843f531b67edb2a90a3ce64092afac190c88a985 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 25 Mar 2019 16:17:00 -0500 Subject: [PATCH 0046/1582] code review fixes --- SoftLayer/CLI/account/event_detail.py | 7 ++++--- SoftLayer/CLI/account/events.py | 3 ++- SoftLayer/CLI/account/invoice_detail.py | 2 +- SoftLayer/CLI/account/invoices.py | 2 ++ SoftLayer/utils.py | 8 ++++++++ tests/CLI/modules/account_tests.py | 8 ++++++++ 6 files changed, 25 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/account/event_detail.py b/SoftLayer/CLI/account/event_detail.py index 445093f15..2c1ee80c2 100644 --- a/SoftLayer/CLI/account/event_detail.py +++ b/SoftLayer/CLI/account/event_detail.py @@ -31,7 +31,8 @@ def cli(env, identifier, ack): def basic_event_table(event): """Formats a basic event table""" - table = formatting.Table(["Id", "Status", "Type", "Start", "End"], title=event.get('subject')) + table = formatting.Table(["Id", "Status", "Type", "Start", "End"], + title=utils.clean_splitlines(event.get('subject'))) table.add_row([ event.get('id'), @@ -47,7 +48,7 @@ def basic_event_table(event): def impacted_table(event): """Formats a basic impacted resources table""" table = formatting.Table([ - "Type", "Id", "hostname", "privateIp", "Label" + "Type", "Id", "Hostname", "PrivateIp", "Label" ]) for item in event.get('impactedResources', []): table.add_row([ @@ -69,4 +70,4 @@ def update_table(event): update_number = update_number + 1 text = update.get('contents') # deals with all the \r\n from the API - click.secho("\n".join(text.splitlines())) + click.secho(utils.clean_splitlines(text)) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index c1c9ff684..5cc91144d 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -44,7 +44,8 @@ def event_table(events): event.get('id'), utils.clean_time(event.get('startDate')), utils.clean_time(event.get('endDate')), - event.get('subject'), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), utils.lookup(event, 'statusCode', 'name'), event.get('acknowledgedFlag'), event.get('updateCount'), diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index dee77dae6..45343184e 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -21,7 +21,7 @@ def cli(env, identifier, details): top_items = manager.get_billing_items(identifier) title = "Invoice %s" % identifier - table = formatting.Table(["Item Id", "category", "description", "Single", + table = formatting.Table(["Item Id", "Category", "Description", "Single", "Monthly", "Create Date", "Location"], title=title) table.align['category'] = 'l' table.align['description'] = 'l' diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index 3047e59d6..1610ed11e 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -30,6 +30,8 @@ def cli(env, limit, closed=False, get_all=False): table.align['Ending Balance'] = 'l' table.align['Invoice Amount'] = 'l' table.align['Items'] = 'l' + if isinstance(invoices, dict): + invoices = [invoices] for invoice in invoices: table.add_row([ invoice.get('id'), diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 918da2b09..f4904adf6 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -290,6 +290,14 @@ def clean_string(string): return " ".join(string.split()) +def clean_splitlines(string): + """Returns a string where \r\n is replaced with \n""" + if string is None: + return '' + else: + return "\n".join(string.splitlines()) + + def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H:%M'): """Easy way to format time strings diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 67575c53c..c495546c8 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -4,6 +4,7 @@ Tests for the user cli command """ +from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing @@ -80,6 +81,13 @@ def test_invoices_all(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=50) + def test_single_invoice(self): + amock = self.set_mock('SoftLayer_Account', 'getInvoices') + amock.return_value = SoftLayer_Account.getInvoices[0] + result = self.run_command(['account', 'invoices', '--limit=1']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getInvoices', limit=1) + # slcli account summary def test_account_summary(self): result = self.run_command(['account', 'summary']) From fa19d5de891a3ae30fa7cc4824d5ed289f83a1d3 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 26 Mar 2019 15:04:46 -0400 Subject: [PATCH 0047/1582] Fix object storage apiType for S3 and Swift. --- SoftLayer/CLI/object_storage/list_accounts.py | 9 ++++++++- SoftLayer/fixtures/SoftLayer_Account.py | 4 ++-- SoftLayer/managers/object_storage.py | 10 +++------- tests/CLI/modules/object_storage_tests.py | 5 +++-- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_accounts.py b/SoftLayer/CLI/object_storage/list_accounts.py index c86ca933f..c61c88e52 100644 --- a/SoftLayer/CLI/object_storage/list_accounts.py +++ b/SoftLayer/CLI/object_storage/list_accounts.py @@ -15,12 +15,19 @@ def cli(env): mgr = SoftLayer.ObjectStorageManager(env.client) accounts = mgr.list_accounts() - table = formatting.Table(['id', 'name']) + table = formatting.Table(['id', 'name', 'apiType']) table.sortby = 'id' + global api_type for account in accounts: + if 'vendorName' in account and 'Swift' == account['vendorName']: + api_type = 'Swift' + elif 'Cleversafe' in account['serviceResource']['name']: + api_type = 'S3' + table.add_row([ account['id'], account['username'], + api_type, ]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index b4bafac92..3425acdf2 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -525,8 +525,8 @@ getNextInvoiceTotalAmount = 2 -getHubNetworkStorage = [{'id': 12345, 'username': 'SLOS12345-1'}, - {'id': 12346, 'username': 'SLOS12345-2'}] +getHubNetworkStorage = [{'id': 12345, 'username': 'SLOS12345-1', 'serviceResource': {'name': 'Cleversafe - US Region'}}, + {'id': 12346, 'username': 'SLOS12345-2', 'vendorName': 'Swift'}] getIscsiNetworkStorage = [{ 'accountId': 1234, diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 393d16db7..b25a457e1 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -6,8 +6,8 @@ :license: MIT, see LICENSE for more details. """ -LIST_ACCOUNTS_MASK = '''mask(SoftLayer_Network_Storage_Hub_Swift)[ - id,username,notes +LIST_ACCOUNTS_MASK = '''mask[ + id,username,notes,vendorName,serviceResource ]''' ENDPOINT_MASK = '''mask(SoftLayer_Network_Storage_Hub_Swift)[ @@ -29,12 +29,8 @@ def __init__(self, client): def list_accounts(self): """Lists your object storage accounts.""" - _filter = { - 'hubNetworkStorage': {'vendorName': {'operation': 'Swift'}}, - } return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK, - filter=_filter) + mask=LIST_ACCOUNTS_MASK) def list_endpoints(self): """Lists the known object storage endpoints.""" diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 0f59c847e..7c8e7ec96 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -16,8 +16,9 @@ def test_list_accounts(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'id': 12345, 'name': 'SLOS12345-1'}, - {'id': 12346, 'name': 'SLOS12345-2'}]) + [{'apiType': 'S3', 'id': 12345, 'name': 'SLOS12345-1'}, + {'apiType': 'Swift', 'id': 12346, 'name': 'SLOS12345-2'}] + ) def test_list_endpoints(self): accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') From 10e23fdd5c0427ad1ff5a5284410c755378a0e6d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 26 Mar 2019 17:08:01 -0400 Subject: [PATCH 0048/1582] Fix object storage apiType for S3 and Swift. --- SoftLayer/CLI/object_storage/list_accounts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_accounts.py b/SoftLayer/CLI/object_storage/list_accounts.py index c61c88e52..c49aecc67 100644 --- a/SoftLayer/CLI/object_storage/list_accounts.py +++ b/SoftLayer/CLI/object_storage/list_accounts.py @@ -17,9 +17,9 @@ def cli(env): accounts = mgr.list_accounts() table = formatting.Table(['id', 'name', 'apiType']) table.sortby = 'id' - global api_type + api_type = None for account in accounts: - if 'vendorName' in account and 'Swift' == account['vendorName']: + if 'vendorName' in account and account['vendorName'] == 'Swift': api_type = 'Swift' elif 'Cleversafe' in account['serviceResource']['name']: api_type = 'S3' From b1752c1dc4fe13fd6a66b8524f18b06002d6ebc6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 27 Mar 2019 18:04:17 -0500 Subject: [PATCH 0049/1582] #1099 updated event_log get to support pagination --- SoftLayer/API.py | 7 ++- SoftLayer/CLI/event_log/get.py | 80 ++++++++++++++++++++------------- SoftLayer/managers/event_log.py | 49 ++++++-------------- 3 files changed, 68 insertions(+), 68 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index c5fd95f3a..b32d84cd4 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -296,12 +296,15 @@ def iter_call(self, service, method, *args, **kwargs): if isinstance(results, list): # Close enough, this makes testing a lot easier results = transports.SoftLayerListResult(results, len(results)) + elif results is None: + yield results, 0 + return else: - yield results + yield results, 1 return for item in results: - yield item + yield item, results.total_count result_count += 1 # Got less results than requested, we are at the end diff --git a/SoftLayer/CLI/event_log/get.py b/SoftLayer/CLI/event_log/get.py index 6e07d7645..19455b562 100644 --- a/SoftLayer/CLI/event_log/get.py +++ b/SoftLayer/CLI/event_log/get.py @@ -6,8 +6,8 @@ import click import SoftLayer +from SoftLayer import utils from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting COLUMNS = ['event', 'object', 'type', 'date', 'username'] @@ -23,44 +23,47 @@ help="The id of the object we want to get event logs for") @click.option('--obj-type', '-t', help="The type of the object we want to get event logs for") -@click.option('--utc-offset', '-z', - help="UTC Offset for searching with dates. The default is -0000") -@click.option('--metadata/--no-metadata', default=False, +@click.option('--utc-offset', '-z', default='-0000', show_default=True, + help="UTC Offset for searching with dates. +/-HHMM format") +@click.option('--metadata/--no-metadata', default=False, show_default=True, help="Display metadata if present") -@click.option('--limit', '-l', default=30, - help="How many results to get in one api call, default is 30.") +@click.option('--limit', '-l', type=click.INT, default=50, show_default=True, + help="Total number of result to return. -1 to return ALL, there may be a LOT of these.") @environment.pass_env def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metadata, limit): """Get Event Logs""" - mgr = SoftLayer.EventLogManager(env.client) - usrmgr = SoftLayer.UserManager(env.client) - request_filter = mgr.build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset) - logs = mgr.get_event_logs(request_filter, log_limit=limit) - - if logs is None: - env.fout('None available.') - return + event_mgr = SoftLayer.EventLogManager(env.client) + user_mgr = SoftLayer.UserManager(env.client) + request_filter = event_mgr.build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset) + logs = event_mgr.get_event_logs(request_filter) + log_time = "%Y-%m-%dT%H:%M:%S.%f%z" + user_data = {} if metadata and 'metadata' not in COLUMNS: COLUMNS.append('metadata') - table = formatting.Table(COLUMNS) + row_count = 0 + for log, rows in logs: + if log is None: + click.secho('No logs available for filter %s.' % request_filter, fg='red') + return - if metadata: - table.align['metadata'] = "l" + if row_count == 0: + if limit < 0: + limit = rows + click.secho("Number of records: %s" % rows, fg='red') + click.secho(", ".join(COLUMNS)) - for log in logs: user = log['userType'] - label = '' - - try: - label = log['label'] - except KeyError: - pass # label is already at default value. - + label = log.get('label', '') if user == "CUSTOMER": - user = usrmgr.get_user(log['userId'], "mask[username]")['username'] + username = user_data.get(log['userId']) + if username is None: + username = user_mgr.get_user(log['userId'], "mask[username]")['username'] + user_data[log['userId']] = username + user = username + if metadata: try: metadata_data = json.dumps(json.loads(log['metaData']), indent=4, sort_keys=True) @@ -69,9 +72,24 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada except ValueError: metadata_data = log['metaData'] - table.add_row([log['eventName'], label, log['objectName'], - log['eventCreateDate'], user, metadata_data]) + click.secho('"{0}","{1}","{2}","{3}","{4}","{5}"'.format( + log['eventName'], + label, + log['objectName'], + utils.clean_time(log['eventCreateDate'], in_format=log_time), + user, + metadata_data) + ) else: - table.add_row([log['eventName'], label, log['objectName'], - log['eventCreateDate'], user]) - env.fout(table) + click.secho('"{0}","{1}","{2}","{3}","{4}"'.format( + log['eventName'], + label, + log['objectName'], + utils.clean_time(log['eventCreateDate'], in_format=log_time), + user) + ) + + row_count = row_count + 1 + if row_count >= limit: + return + diff --git a/SoftLayer/managers/event_log.py b/SoftLayer/managers/event_log.py index 9e36471c3..655ddd5f4 100644 --- a/SoftLayer/managers/event_log.py +++ b/SoftLayer/managers/event_log.py @@ -17,16 +17,23 @@ class EventLogManager(object): """ def __init__(self, client): + self.client = client self.event_log = client['Event_Log'] - def get_event_logs(self, request_filter, log_limit=10): + def get_event_logs(self, request_filter={}, log_limit=50, iter=True): """Returns a list of event logs :param dict request_filter: filter dict + :param int log_limit: number of results to get in one API call + :param bool iter: False will only make one API call for log_limit results. + True will keep making API calls until all logs have been retreived. There may be a lot of these. :returns: List of event logs """ - results = self.event_log.getAllObjects(filter=request_filter, limit=log_limit) - return results + if iter: + # Call iter_call directly as this returns the actual generator + return self.client.iter_call('Event_Log', 'getAllObjects', filter=request_filter, limit=log_limit) + return self.client.call('Event_Log', 'getAllObjects', filter=request_filter, limit=log_limit) + def get_event_log_types(self): """Returns a list of event log types @@ -36,28 +43,6 @@ def get_event_log_types(self): results = self.event_log.getAllEventObjectNames() return results - def get_event_logs_by_type(self, event_type): - """Returns a list of event logs, filtered on the 'objectName' field - - :param string event_type: The event type we want to filter on - :returns: List of event logs, filtered on the 'objectName' field - """ - request_filter = {} - request_filter['objectName'] = {'operation': event_type} - - return self.event_log.getAllObjects(filter=request_filter) - - def get_event_logs_by_event_name(self, event_name): - """Returns a list of event logs, filtered on the 'eventName' field - - :param string event_type: The event type we want to filter on - :returns: List of event logs, filtered on the 'eventName' field - """ - request_filter = {} - request_filter['eventName'] = {'operation': event_name} - - return self.event_log.getAllObjects(filter=request_filter) - @staticmethod def build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset): """Returns a query filter that can be passed into EventLogManager.get_event_logs @@ -73,8 +58,8 @@ def build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset): :returns: dict: The generated query filter """ - if not date_min and not date_max and not obj_event and not obj_id and not obj_type: - return None + if not any([date_min, date_max, obj_event, obj_id, obj_type]): + return {} request_filter = {} @@ -82,15 +67,9 @@ def build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset): request_filter['eventCreateDate'] = utils.event_log_filter_between_date(date_min, date_max, utc_offset) else: if date_min: - request_filter['eventCreateDate'] = utils.event_log_filter_greater_than_date( - date_min, - utc_offset - ) + request_filter['eventCreateDate'] = utils.event_log_filter_greater_than_date(date_min, utc_offset) elif date_max: - request_filter['eventCreateDate'] = utils.event_log_filter_less_than_date( - date_max, - utc_offset - ) + request_filter['eventCreateDate'] = utils.event_log_filter_less_than_date(date_max, utc_offset) if obj_event: request_filter['eventName'] = {'operation': obj_event} From f7e80258a6fcb6ea5573ee6d37ae28166a1c819b Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 28 Mar 2019 18:36:02 -0500 Subject: [PATCH 0050/1582] fixed a bunch of tox related errors --- SoftLayer/API.py | 9 +- SoftLayer/CLI/event_log/get.py | 56 ++--- SoftLayer/managers/event_log.py | 9 +- SoftLayer/managers/network.py | 6 +- tests/CLI/modules/event_log_tests.py | 315 ++------------------------- tests/managers/event_log_tests.py | 68 ++---- tests/managers/network_tests.py | 66 ++---- 7 files changed, 74 insertions(+), 455 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index b32d84cd4..e65da3884 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,8 +6,10 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +from __future__ import generators import warnings + from SoftLayer import auth as slauth from SoftLayer import config from SoftLayer import consts @@ -296,15 +298,12 @@ def iter_call(self, service, method, *args, **kwargs): if isinstance(results, list): # Close enough, this makes testing a lot easier results = transports.SoftLayerListResult(results, len(results)) - elif results is None: - yield results, 0 - return else: - yield results, 1 + yield results return for item in results: - yield item, results.total_count + yield item result_count += 1 # Got less results than requested, we are at the end diff --git a/SoftLayer/CLI/event_log/get.py b/SoftLayer/CLI/event_log/get.py index 19455b562..a57a53ec0 100644 --- a/SoftLayer/CLI/event_log/get.py +++ b/SoftLayer/CLI/event_log/get.py @@ -1,15 +1,11 @@ """Get Event Logs.""" # :license: MIT, see LICENSE for more details. -import json - import click import SoftLayer -from SoftLayer import utils from SoftLayer.CLI import environment - -COLUMNS = ['event', 'object', 'type', 'date', 'username'] +from SoftLayer import utils @click.command() @@ -32,6 +28,7 @@ @environment.pass_env def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metadata, limit): """Get Event Logs""" + columns = ['Event', 'Object', 'Type', 'Date', 'Username'] event_mgr = SoftLayer.EventLogManager(env.client) user_mgr = SoftLayer.UserManager(env.client) @@ -40,21 +37,16 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada log_time = "%Y-%m-%dT%H:%M:%S.%f%z" user_data = {} - if metadata and 'metadata' not in COLUMNS: - COLUMNS.append('metadata') + if metadata: + columns.append('Metadata') row_count = 0 - for log, rows in logs: + click.secho(", ".join(columns)) + for log in logs: if log is None: click.secho('No logs available for filter %s.' % request_filter, fg='red') return - if row_count == 0: - if limit < 0: - limit = rows - click.secho("Number of records: %s" % rows, fg='red') - click.secho(", ".join(COLUMNS)) - user = log['userType'] label = log.get('label', '') if user == "CUSTOMER": @@ -65,31 +57,23 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada user = username if metadata: - try: - metadata_data = json.dumps(json.loads(log['metaData']), indent=4, sort_keys=True) - if env.format == "table": - metadata_data = metadata_data.strip("{}\n\t") - except ValueError: - metadata_data = log['metaData'] + metadata_data = log['metaData'].strip("\n\t") - click.secho('"{0}","{1}","{2}","{3}","{4}","{5}"'.format( - log['eventName'], - label, - log['objectName'], - utils.clean_time(log['eventCreateDate'], in_format=log_time), - user, - metadata_data) - ) + click.secho("'{0}','{1}','{2}','{3}','{4}','{5}'".format( + log['eventName'], + label, + log['objectName'], + utils.clean_time(log['eventCreateDate'], in_format=log_time), + user, + metadata_data)) else: - click.secho('"{0}","{1}","{2}","{3}","{4}"'.format( - log['eventName'], - label, - log['objectName'], - utils.clean_time(log['eventCreateDate'], in_format=log_time), - user) - ) + click.secho("'{0}','{1}','{2}','{3}','{4}'".format( + log['eventName'], + label, + log['objectName'], + utils.clean_time(log['eventCreateDate'], in_format=log_time), + user)) row_count = row_count + 1 if row_count >= limit: return - diff --git a/SoftLayer/managers/event_log.py b/SoftLayer/managers/event_log.py index 655ddd5f4..83720fede 100644 --- a/SoftLayer/managers/event_log.py +++ b/SoftLayer/managers/event_log.py @@ -20,21 +20,20 @@ def __init__(self, client): self.client = client self.event_log = client['Event_Log'] - def get_event_logs(self, request_filter={}, log_limit=50, iter=True): + def get_event_logs(self, request_filter=None, log_limit=20, iterator=True): """Returns a list of event logs :param dict request_filter: filter dict :param int log_limit: number of results to get in one API call - :param bool iter: False will only make one API call for log_limit results. + :param bool iterator: False will only make one API call for log_limit results. True will keep making API calls until all logs have been retreived. There may be a lot of these. :returns: List of event logs """ - if iter: + if iterator: # Call iter_call directly as this returns the actual generator return self.client.iter_call('Event_Log', 'getAllObjects', filter=request_filter, limit=log_limit) return self.client.call('Event_Log', 'getAllObjects', filter=request_filter, limit=log_limit) - def get_event_log_types(self): """Returns a list of event log types @@ -44,7 +43,7 @@ def get_event_log_types(self): return results @staticmethod - def build_filter(date_min, date_max, obj_event, obj_id, obj_type, utc_offset): + def build_filter(date_min=None, date_max=None, obj_event=None, obj_id=None, obj_type=None, utc_offset=None): """Returns a query filter that can be passed into EventLogManager.get_event_logs :param string date_min: Lower bound date in MM/DD/YYYY format diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 48edbefa7..ae42ca405 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -575,14 +575,16 @@ def _get_cci_event_logs(self): event_log_mgr = event_log.EventLogManager(self.client) # Get CCI Event Logs - return event_log_mgr.get_event_logs_by_type('CCI') + _filter = event_log_mgr.build_filter(obj_type='CCI') + return event_log_mgr.get_event_logs(request_filter=_filter) def _get_security_group_event_logs(self): # Load the event log manager event_log_mgr = event_log.EventLogManager(self.client) # Get CCI Event Logs - return event_log_mgr.get_event_logs_by_type('Security Group') + _filter = event_log_mgr.build_filter(obj_type='Security Group') + return event_log_mgr.get_event_logs(request_filter=_filter) def resolve_global_ip_ids(self, identifier): """Resolve global ip ids.""" diff --git a/tests/CLI/modules/event_log_tests.py b/tests/CLI/modules/event_log_tests.py index b36ff8530..2e6e2fb24 100644 --- a/tests/CLI/modules/event_log_tests.py +++ b/tests/CLI/modules/event_log_tests.py @@ -6,325 +6,40 @@ import json -from SoftLayer.CLI import formatting from SoftLayer import testing class EventLogTests(testing.TestCase): - def test_get_event_log_with_metadata(self): - expected = [ - { - 'date': '2017-10-23T14:22:36.221541-05:00', - 'event': 'Disable Port', - 'object': 'test.softlayer.com', - 'username': 'SYSTEM', - 'type': 'CCI', - 'metadata': '' - }, - { - 'date': '2017-10-18T09:40:41.830338-05:00', - 'event': 'Security Group Rule Added', - 'object': 'test.softlayer.com', - 'username': 'SL12345-test', - 'type': 'CCI', - 'metadata': json.dumps(json.loads( - '{"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"53d0b91d392864e062f4958",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' - '"securityGroupName":"test_SG"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T09:40:32.238869-05:00', - 'event': 'Security Group Added', - 'object': 'test.softlayer.com', - 'username': 'SL12345-test', - 'type': 'CCI', - 'metadata': json.dumps(json.loads( - '{"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"96c9b47b9e102d2e1d81fba",' - '"securityGroupId":"200",' - '"securityGroupName":"test_SG"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:42:13.089536-05:00', - 'event': 'Security Group Rule(s) Removed', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"requestId":"2abda7ca97e5a1444cae0b9",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:42:11.679736-05:00', - 'event': 'Network Component Removed from Security Group', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"fullyQualifiedDomainName":"test.softlayer.com",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:41:49.802498-05:00', - 'event': 'Security Group Rule(s) Added', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"requestId":"0a293c1c3e59e4471da6495",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:41:42.176328-05:00', - 'event': 'Network Component Added to Security Group', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"fullyQualifiedDomainName":"test.softlayer.com",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"4709e02ad42c83f80345904"}' - ), - indent=4, - sort_keys=True - ) - } - ] + def test_get_event_log_with_metadata(self): result = self.run_command(['event-log', 'get', '--metadata']) self.assert_no_fail(result) - self.assertEqual(expected, json.loads(result.output)) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') + self.assertIn('Metadata', result.output) def test_get_event_log_without_metadata(self): - expected = [ - { - 'date': '2017-10-23T14:22:36.221541-05:00', - 'event': 'Disable Port', - 'username': 'SYSTEM', - 'type': 'CCI', - 'object': 'test.softlayer.com' - }, - { - 'date': '2017-10-18T09:40:41.830338-05:00', - 'event': 'Security Group Rule Added', - 'username': 'SL12345-test', - 'type': 'CCI', - 'object': 'test.softlayer.com' - }, - { - 'date': '2017-10-18T09:40:32.238869-05:00', - 'event': 'Security Group Added', - 'username': 'SL12345-test', - 'type': 'CCI', - 'object': 'test.softlayer.com' - }, - { - 'date': '2017-10-18T10:42:13.089536-05:00', - 'event': 'Security Group Rule(s) Removed', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'object': 'test_SG' - }, - { - 'date': '2017-10-18T10:42:11.679736-05:00', - 'event': 'Network Component Removed from Security Group', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'object': 'test_SG' - }, - { - 'date': '2017-10-18T10:41:49.802498-05:00', - 'event': 'Security Group Rule(s) Added', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'object': 'test_SG' - }, - { - 'date': '2017-10-18T10:41:42.176328-05:00', - 'event': 'Network Component Added to Security Group', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'object': 'test_SG' - } - ] - - result = self.run_command(['event-log', 'get']) - - self.assert_no_fail(result) - self.assertEqual(expected, json.loads(result.output)) - - def test_get_event_table(self): - table_fix = formatting.Table(['event', 'object', 'type', 'date', 'username', 'metadata']) - table_fix.align['metadata'] = "l" - expected = [ - { - 'date': '2017-10-23T14:22:36.221541-05:00', - 'event': 'Disable Port', - 'object': 'test.softlayer.com', - 'username': 'SYSTEM', - 'type': 'CCI', - 'metadata': '' - }, - { - 'date': '2017-10-18T09:40:41.830338-05:00', - 'event': 'Security Group Rule Added', - 'object': 'test.softlayer.com', - 'username': 'SL12345-test', - 'type': 'CCI', - 'metadata': json.dumps(json.loads( - '{"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"53d0b91d392864e062f4958",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"100"}],"securityGroupId":"200",' - '"securityGroupName":"test_SG"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T09:40:32.238869-05:00', - 'event': 'Security Group Added', - 'object': 'test.softlayer.com', - 'username': 'SL12345-test', - 'type': 'CCI', - 'metadata': json.dumps(json.loads( - '{"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"96c9b47b9e102d2e1d81fba",' - '"securityGroupId":"200",' - '"securityGroupName":"test_SG"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:42:13.089536-05:00', - 'event': 'Security Group Rule(s) Removed', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"requestId":"2abda7ca97e5a1444cae0b9",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:42:11.679736-05:00', - 'event': 'Network Component Removed from Security Group', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"fullyQualifiedDomainName":"test.softlayer.com",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"6b9a87a9ab8ac9a22e87a00"}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:41:49.802498-05:00', - 'event': 'Security Group Rule(s) Added', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"requestId":"0a293c1c3e59e4471da6495",' - '"rules":[{"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMax":2001,"portRangeMin":2000,"protocol":"tcp",' - '"remoteGroupId":null,"remoteIp":null,"ruleId":"800"}]}' - ), - indent=4, - sort_keys=True - ) - }, - { - 'date': '2017-10-18T10:41:42.176328-05:00', - 'event': 'Network Component Added to Security Group', - 'object': 'test_SG', - 'username': 'SL12345-test', - 'type': 'Security Group', - 'metadata': json.dumps(json.loads( - '{"fullyQualifiedDomainName":"test.softlayer.com",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"4709e02ad42c83f80345904"}' - ), - indent=4, - sort_keys=True - ) - } - ] - - for log in expected: - table_fix.add_row([log['event'], log['object'], log['type'], log['date'], - log['username'], log['metadata'].strip("{}\n\t")]) - expected_output = formatting.format_output(table_fix) + '\n' - - result = self.run_command(args=['event-log', 'get', '--metadata'], fmt='table') - + result = self.run_command(['event-log', 'get', '--no-metadata']) self.assert_no_fail(result) - self.assertEqual(expected_output, result.output) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') + self.assert_called_with('SoftLayer_User_Customer', 'getObject', identifier=400) + self.assertNotIn('Metadata', result.output) def test_get_event_log_empty(self): mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects') mock.return_value = None result = self.run_command(['event-log', 'get']) + expected = 'Event, Object, Type, Date, Username\n' \ + 'No logs available for filter {}.\n' + self.assert_no_fail(result) + self.assertEqual(expected, result.output) - self.assertEqual(mock.call_count, 1) + def test_get_event_log_over_limit(self): + result = self.run_command(['event-log', 'get', '-l 1']) self.assert_no_fail(result) - self.assertEqual('"None available."\n', result.output) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') + self.assertEqual(2, result.output.count("\n")) def test_get_event_log_types(self): expected = [ diff --git a/tests/managers/event_log_tests.py b/tests/managers/event_log_tests.py index 9a933e0d8..e5c220835 100644 --- a/tests/managers/event_log_tests.py +++ b/tests/managers/event_log_tests.py @@ -15,74 +15,32 @@ def set_up(self): self.event_log = SoftLayer.EventLogManager(self.client) def test_get_event_logs(self): - result = self.event_log.get_event_logs(None) + # Cast to list to force generator to get all objects + result = list(self.event_log.get_event_logs()) expected = fixtures.SoftLayer_Event_Log.getAllObjects self.assertEqual(expected, result) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') - def test_get_event_log_types(self): - result = self.event_log.get_event_log_types() - - expected = fixtures.SoftLayer_Event_Log.getAllEventObjectNames - self.assertEqual(expected, result) - - def test_get_event_logs_by_type(self): - expected = [ - { - 'accountId': 100, - 'eventCreateDate': '2017-10-23T14:22:36.221541-05:00', - 'eventName': 'Disable Port', - 'ipAddress': '192.168.0.1', - 'label': 'test.softlayer.com', - 'metaData': '', - 'objectId': 300, - 'objectName': 'CCI', - 'traceId': '100', - 'userId': '', - 'userType': 'SYSTEM' - } - ] - - mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects') - mock.return_value = expected - - result = self.event_log.get_event_logs_by_type('CCI') + def test_get_event_logs_no_iteration(self): + # Cast to list to force generator to get all objects + result = self.event_log.get_event_logs(iterator=False) + expected = fixtures.SoftLayer_Event_Log.getAllObjects self.assertEqual(expected, result) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') - def test_get_event_logs_by_event_name(self): - expected = [ - { - 'accountId': 100, - 'eventCreateDate': '2017-10-18T09:40:32.238869-05:00', - 'eventName': 'Security Group Added', - 'ipAddress': '192.168.0.1', - 'label': 'test.softlayer.com', - 'metaData': '{"securityGroupId":"200",' - '"securityGroupName":"test_SG",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"96c9b47b9e102d2e1d81fba"}', - 'objectId': 300, - 'objectName': 'CCI', - 'traceId': '59e767e03a57e', - 'userId': 400, - 'userType': 'CUSTOMER', - 'username': 'user' - } - ] - - mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects') - mock.return_value = expected - - result = self.event_log.get_event_logs_by_event_name('Security Group Added') + def test_get_event_log_types(self): + result = self.event_log.get_event_log_types() + expected = fixtures.SoftLayer_Event_Log.getAllEventObjectNames self.assertEqual(expected, result) + self.assert_called_with('SoftLayer_Event_Log', 'getAllEventObjectNames') def test_build_filter_no_args(self): result = self.event_log.build_filter(None, None, None, None, None, None) - self.assertEqual(result, None) + self.assertEqual(result, {}) def test_build_filter_min_date(self): expected = { diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index f9f5ed308..bb2823f7c 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -5,6 +5,8 @@ :license: MIT, see LICENSE for more details. """ import mock +import sys +import unittest import SoftLayer from SoftLayer import fixtures @@ -609,60 +611,20 @@ def test_get_event_logs_by_request_id(self): self.assertEqual(expected, result) + @unittest.skipIf(sys.version_info < (3, 6), "__next__ doesn't work in python 2") def test_get_security_group_event_logs(self): - expected = [ - { - 'accountId': 100, - 'eventCreateDate': '2017-10-18T10:42:13.089536-05:00', - 'eventName': 'Security Group Rule(s) Removed', - 'ipAddress': '192.168.0.1', - 'label': 'test_SG', - 'metaData': '{"requestId":"96c9b47b9e102d2e1d81fba",' - '"rules":[{"ruleId":"800",' - '"remoteIp":null,"remoteGroupId":null,"direction":"ingress",' - '"ethertype":"IPv4",' - '"portRangeMin":2000,"portRangeMax":2001,"protocol":"tcp"}]}', - 'objectId': 700, - 'objectName': 'Security Group', - 'traceId': '59e7765515e28', - 'userId': 400, - 'userType': 'CUSTOMER', - 'username': 'user' - } - ] - - mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects') - mock.return_value = expected - result = self.network._get_security_group_event_logs() + # Event log now returns a generator, so you have to get a result for it to make an API call + log = result.__next__() + _filter = {'objectName': {'operation': 'Security Group'}} + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=_filter) + self.assertEqual(100, log['accountId']) - self.assertEqual(expected, result) - + @unittest.skipIf(sys.version_info < (3, 6), "__next__ doesn't work in python 2") def test_get_cci_event_logs(self): - expected = [ - { - 'accountId': 100, - 'eventCreateDate': '2017-10-18T09:40:32.238869-05:00', - 'eventName': 'Security Group Added', - 'ipAddress': '192.168.0.1', - 'label': 'test.softlayer.com', - 'metaData': '{"securityGroupId":"200",' - '"securityGroupName":"test_SG",' - '"networkComponentId":"100",' - '"networkInterfaceType":"public",' - '"requestId":"96c9b47b9e102d2e1d81fba"}', - 'objectId': 300, - 'objectName': 'CCI', - 'traceId': '59e767e03a57e', - 'userId': 400, - 'userType': 'CUSTOMER', - 'username': 'user' - } - ] - - mock = self.set_mock('SoftLayer_Event_Log', 'getAllObjects') - mock.return_value = expected - result = self.network._get_cci_event_logs() - - self.assertEqual(expected, result) + # Event log now returns a generator, so you have to get a result for it to make an API call + log = result.__next__() + _filter = {'objectName': {'operation': 'CCI'}} + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=_filter) + self.assertEqual(100, log['accountId']) From 67ee3621e7458ea12361b4571c47b9c99ae3d2a0 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 3 Apr 2019 17:54:34 -0500 Subject: [PATCH 0051/1582] added some more documentation around event-logs --- SoftLayer/CLI/call_api.py | 26 ++++++++++++------------ SoftLayer/CLI/event_log/get.py | 6 +++++- SoftLayer/managers/event_log.py | 13 ++++++++++-- docs/api/managers/event_log.rst | 5 +++++ docs/cli.rst | 9 +++------ docs/cli/call_api.rst | 9 +++++++++ docs/cli/event_log.rst | 36 +++++++++++++++++++++++++++++++++ 7 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 docs/api/managers/event_log.rst create mode 100644 docs/cli/call_api.rst create mode 100644 docs/cli/event_log.rst diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index 0adb4fa31..6e16a2a77 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -87,19 +87,19 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, output_python=False): """Call arbitrary API endpoints with the given SERVICE and METHOD. - \b - Examples: - slcli call-api Account getObject - slcli call-api Account getVirtualGuests --limit=10 --mask=id,hostname - slcli call-api Virtual_Guest getObject --id=12345 - slcli call-api Metric_Tracking_Object getBandwidthData --id=1234 \\ - "2015-01-01 00:00:00" "2015-01-1 12:00:00" public - slcli call-api Account getVirtualGuests \\ - -f 'virtualGuests.datacenter.name=dal05' \\ - -f 'virtualGuests.maxCpu=4' \\ - --mask=id,hostname,datacenter.name,maxCpu - slcli call-api Account getVirtualGuests \\ - -f 'virtualGuests.datacenter.name IN dal05,sng01' + Example:: + + slcli call-api Account getObject + slcli call-api Account getVirtualGuests --limit=10 --mask=id,hostname + slcli call-api Virtual_Guest getObject --id=12345 + slcli call-api Metric_Tracking_Object getBandwidthData --id=1234 \\ + "2015-01-01 00:00:00" "2015-01-1 12:00:00" public + slcli call-api Account getVirtualGuests \\ + -f 'virtualGuests.datacenter.name=dal05' \\ + -f 'virtualGuests.maxCpu=4' \\ + --mask=id,hostname,datacenter.name,maxCpu + slcli call-api Account getVirtualGuests \\ + -f 'virtualGuests.datacenter.name IN dal05,sng01' """ args = [service, method] + list(parameters) diff --git a/SoftLayer/CLI/event_log/get.py b/SoftLayer/CLI/event_log/get.py index a57a53ec0..983109052 100644 --- a/SoftLayer/CLI/event_log/get.py +++ b/SoftLayer/CLI/event_log/get.py @@ -27,7 +27,11 @@ help="Total number of result to return. -1 to return ALL, there may be a LOT of these.") @environment.pass_env def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metadata, limit): - """Get Event Logs""" + """Get Event Logs + + Example: + slcli event-log get -d 01/01/2019 -D 02/01/2019 -t User -l 10 + """ columns = ['Event', 'Object', 'Type', 'Date', 'Username'] event_mgr = SoftLayer.EventLogManager(env.client) diff --git a/SoftLayer/managers/event_log.py b/SoftLayer/managers/event_log.py index 83720fede..cc0a7f5cd 100644 --- a/SoftLayer/managers/event_log.py +++ b/SoftLayer/managers/event_log.py @@ -1,6 +1,6 @@ """ SoftLayer.event_log - ~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~ Network Manager/helpers :license: MIT, see LICENSE for more details. @@ -23,11 +23,20 @@ def __init__(self, client): def get_event_logs(self, request_filter=None, log_limit=20, iterator=True): """Returns a list of event logs + Example:: + + event_mgr = SoftLayer.EventLogManager(env.client) + request_filter = event_mgr.build_filter(date_min="01/01/2019", date_max="02/01/2019") + logs = event_mgr.get_event_logs(request_filter) + for log in logs: + print("Event Name: {}".format(log['eventName'])) + + :param dict request_filter: filter dict :param int log_limit: number of results to get in one API call :param bool iterator: False will only make one API call for log_limit results. True will keep making API calls until all logs have been retreived. There may be a lot of these. - :returns: List of event logs + :returns: List of event logs. If iterator=True, will return a python generator object instead. """ if iterator: # Call iter_call directly as this returns the actual generator diff --git a/docs/api/managers/event_log.rst b/docs/api/managers/event_log.rst new file mode 100644 index 000000000..41adfeaa4 --- /dev/null +++ b/docs/api/managers/event_log.rst @@ -0,0 +1,5 @@ +.. _event_log: + +.. automodule:: SoftLayer.managers.event_log + :members: + :inherited-members: diff --git a/docs/cli.rst b/docs/cli.rst index 709741aa1..ebd62741e 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -11,13 +11,9 @@ functionality not fully documented here. .. toctree:: :maxdepth: 2 + :glob: - cli/account - cli/vs - cli/hardware - cli/ordering - cli/users - cli/ipsec + cli/* .. _config_setup: @@ -179,3 +175,4 @@ Most commands will take in additional options/arguments. To see all available ac --tags TEXT Show instances that have one of these comma- separated tags --help Show this message and exit. + diff --git a/docs/cli/call_api.rst b/docs/cli/call_api.rst new file mode 100644 index 000000000..e309f16eb --- /dev/null +++ b/docs/cli/call_api.rst @@ -0,0 +1,9 @@ +.. _cli_call_api: + +Call API +======== + + +.. click:: SoftLayer.CLI.call_api:cli + :prog: call-api + :show-nested: diff --git a/docs/cli/event_log.rst b/docs/cli/event_log.rst new file mode 100644 index 000000000..47c7639e5 --- /dev/null +++ b/docs/cli/event_log.rst @@ -0,0 +1,36 @@ +.. _cli_event_log: + +Event-Log Commands +==================== + + +.. click:: SoftLayer.CLI.event_log.get:cli + :prog: event-log get + :show-nested: + +There are usually quite a few events on an account, so be careful when using the `--limit -1` option. The command will automatically break requests out into smaller sub-requests, but this command may take a very long time to complete. It will however print out data as it comes in. + +.. click:: SoftLayer.CLI.event_log.types:cli + :prog: event-log types + :show-nested: + + +Currently the types are as follows, more may be added in the future. +:: + + :......................: + : types : + :......................: + : Account : + : CDN : + : User : + : Bare Metal Instance : + : API Authentication : + : Server : + : CCI : + : Image : + : Bluemix LB : + : Facility : + : Cloud Object Storage : + : Security Group : + :......................: \ No newline at end of file From 018400efc3a83c6f0b2fffb013212f2637779a45 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 4 Apr 2019 16:05:22 -0500 Subject: [PATCH 0052/1582] fixed event-log get --limit -1 only reporting 1 event --- SoftLayer/CLI/event_log/get.py | 2 +- tests/CLI/modules/event_log_tests.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/event_log/get.py b/SoftLayer/CLI/event_log/get.py index 983109052..3880086f2 100644 --- a/SoftLayer/CLI/event_log/get.py +++ b/SoftLayer/CLI/event_log/get.py @@ -79,5 +79,5 @@ def cli(env, date_min, date_max, obj_event, obj_id, obj_type, utc_offset, metada user)) row_count = row_count + 1 - if row_count >= limit: + if row_count >= limit and limit != -1: return diff --git a/tests/CLI/modules/event_log_tests.py b/tests/CLI/modules/event_log_tests.py index 2e6e2fb24..d22847317 100644 --- a/tests/CLI/modules/event_log_tests.py +++ b/tests/CLI/modules/event_log_tests.py @@ -85,3 +85,9 @@ def test_get_event_log_types(self): self.assert_no_fail(result) self.assertEqual(expected, json.loads(result.output)) + + def test_get_unlimited_events(self): + result = self.run_command(['event-log', 'get', '-l -1']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects') + self.assertEqual(8, result.output.count("\n")) From 82f5c79b31d30bb1f46a3f85b6729026ae7cfff4 Mon Sep 17 00:00:00 2001 From: "Albert J. Camacho" Date: Fri, 5 Apr 2019 16:06:08 -0400 Subject: [PATCH 0053/1582] 872 Column 'name' renamed to 'hostname' --- SoftLayer/CLI/report/bandwidth.py | 2 +- tests/CLI/modules/report_tests.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 47f9fdbc1..f7f28e00c 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -200,7 +200,7 @@ def cli(env, start, end, sortby): table = formatting.Table([ 'type', - 'name', + 'hostname', 'public_in', 'public_out', 'private_in', diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index f57d87120..3d580edad 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -96,7 +96,7 @@ def test_bandwidth_report(self): stripped_output = '[' + result.output.split('[', 1)[1] self.assertEqual([ { - 'name': 'pool1', + 'hostname': 'pool1', 'pool': None, 'private_in': 30, 'private_out': 40, @@ -104,7 +104,7 @@ def test_bandwidth_report(self): 'public_out': 20, 'type': 'pool', }, { - 'name': 'pool3', + 'hostname': 'pool3', 'pool': None, 'private_in': 30, 'private_out': 40, @@ -112,7 +112,7 @@ def test_bandwidth_report(self): 'public_out': 20, 'type': 'pool', }, { - 'name': 'host1', + 'hostname': 'host1', 'pool': None, 'private_in': 30, 'private_out': 40, @@ -120,7 +120,7 @@ def test_bandwidth_report(self): 'public_out': 20, 'type': 'virtual', }, { - 'name': 'host3', + 'hostname': 'host3', 'pool': 2, 'private_in': 30, 'private_out': 40, @@ -128,7 +128,7 @@ def test_bandwidth_report(self): 'public_out': 20, 'type': 'virtual', }, { - 'name': 'host1', + 'hostname': 'host1', 'pool': None, 'private_in': 30, 'private_out': 40, @@ -136,7 +136,7 @@ def test_bandwidth_report(self): 'public_out': 20, 'type': 'hardware', }, { - 'name': 'host3', + 'hostname': 'host3', 'pool': None, 'private_in': 30, 'private_out': 40, From 23406bbf32e657e95144bf1759ac8d6bb3ded6f6 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Mon, 8 Apr 2019 18:30:20 -0400 Subject: [PATCH 0054/1582] #1129 fixed issue in slcli subnet create --- SoftLayer/CLI/subnet/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/subnet/create.py b/SoftLayer/CLI/subnet/create.py index 0f81c574b..45c3f4619 100644 --- a/SoftLayer/CLI/subnet/create.py +++ b/SoftLayer/CLI/subnet/create.py @@ -13,7 +13,7 @@ @click.argument('network', type=click.Choice(['public', 'private'])) @click.argument('quantity', type=click.INT) @click.argument('vlan-id') -@click.option('--v6', '--ipv6', is_flag=True, help="Order IPv6 Addresses") +@click.option('--ipv6', '--v6', is_flag=True, help="Order IPv6 Addresses") @click.option('--test', is_flag=True, help="Do not order the subnet; just get a quote") From 1b581fa3d85386c07fcef2038be253a49ea66f5b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Apr 2019 12:07:42 -0400 Subject: [PATCH 0055/1582] Fix object storage credentials. --- .../CLI/object_storage/credential/__init__.py | 42 ++++++++++ .../CLI/object_storage/credential/create.py | 28 +++++++ .../CLI/object_storage/credential/delete.py | 22 ++++++ .../CLI/object_storage/credential/limit.py | 24 ++++++ .../CLI/object_storage/credential/list.py | 29 +++++++ SoftLayer/CLI/routes.py | 2 + ..._Network_Storage_Hub_Cleversafe_Account.py | 39 ++++++++++ SoftLayer/managers/object_storage.py | 44 +++++++++++ tests/CLI/modules/object_storage_tests.py | 70 +++++++++++++++++ tests/managers/object_storage_tests.py | 77 +++++++++++++++++++ 10 files changed, 377 insertions(+) create mode 100644 SoftLayer/CLI/object_storage/credential/__init__.py create mode 100644 SoftLayer/CLI/object_storage/credential/create.py create mode 100644 SoftLayer/CLI/object_storage/credential/delete.py create mode 100644 SoftLayer/CLI/object_storage/credential/limit.py create mode 100644 SoftLayer/CLI/object_storage/credential/list.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py diff --git a/SoftLayer/CLI/object_storage/credential/__init__.py b/SoftLayer/CLI/object_storage/credential/__init__.py new file mode 100644 index 000000000..1f71754bb --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/__init__.py @@ -0,0 +1,42 @@ +"""Manages Object Storage S3 Credentials.""" +# :license: MIT, see LICENSE for more details. + +import importlib +import os + +import click + +CONTEXT = {'help_option_names': ['-h', '--help'], + 'max_content_width': 999} + + +class CapacityCommands(click.MultiCommand): + """Loads module for object storage S3 credentials related commands.""" + + def __init__(self, **attrs): + click.MultiCommand.__init__(self, **attrs) + self.path = os.path.dirname(__file__) + + def list_commands(self, ctx): + """List all sub-commands.""" + commands = [] + for filename in os.listdir(self.path): + if filename == '__init__.py': + continue + if filename.endswith('.py'): + commands.append(filename[:-3].replace("_", "-")) + commands.sort() + return commands + + def get_command(self, ctx, cmd_name): + """Get command for click.""" + path = "%s.%s" % (__name__, cmd_name) + path = path.replace("-", "_") + module = importlib.import_module(path) + return getattr(module, 'cli') + + +# Required to get the sub-sub-sub command to work. +@click.group(cls=CapacityCommands, context_settings=CONTEXT) +def cli(): + """Base command for all object storage credentials S3 related concerns""" diff --git a/SoftLayer/CLI/object_storage/credential/create.py b/SoftLayer/CLI/object_storage/credential/create.py new file mode 100644 index 000000000..934ac7651 --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/create.py @@ -0,0 +1,28 @@ +"""Create credentials for an IBM Cloud Object Storage Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Create credentials for an IBM Cloud Object Storage Account""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + credential = mgr.create_credential(identifier) + table = formatting.Table(['id', 'password', 'username', 'type_name']) + table.sortby = 'id' + table.add_row([ + credential['id'], + credential['password'], + credential['username'], + credential['type']['name'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py new file mode 100644 index 000000000..c5a9ab682 --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -0,0 +1,22 @@ +"""Delete the credential of an Object Storage Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment, exceptions + + +@click.command() +@click.argument('identifier') +@click.option('--credential_id', '-id', type=click.INT, + help="This is the credential id associated with the volume") +@environment.pass_env +def cli(env, identifier, credential_id): + """Delete the credential of an Object Storage Account.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + credential = mgr.delete_credential(identifier, credential_id=credential_id) + + if credential: + env.fout("The credential was deleted successful") diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py new file mode 100644 index 000000000..96d293eae --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -0,0 +1,24 @@ +""" Credential limits for this IBM Cloud Object Storage account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """ Credential limits for this IBM Cloud Object Storage account.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + limit = mgr.limit_credential(identifier) + table = formatting.Table(['limit']) + table.add_row([ + limit, + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/list.py b/SoftLayer/CLI/object_storage/credential/list.py new file mode 100644 index 000000000..2cf81ca3c --- /dev/null +++ b/SoftLayer/CLI/object_storage/credential/list.py @@ -0,0 +1,29 @@ +"""Retrieve credentials used for generating an AWS signature. Max of 2.""" +# :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): + """Retrieve credentials used for generating an AWS signature. Max of 2.""" + + mgr = SoftLayer.ObjectStorageManager(env.client) + list = mgr.list_credential(identifier) + table = formatting.Table(['id', 'password', 'username', 'type_name']) + + for credential in list: + table.add_row([ + credential['id'], + credential['password'], + credential['username'], + credential['type']['name'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c04233c52..97551aeff 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -199,6 +199,8 @@ 'SoftLayer.CLI.object_storage.list_accounts:cli'), ('object-storage:endpoints', 'SoftLayer.CLI.object_storage.list_endpoints:cli'), + ('object-storage:credential', + 'SoftLayer.CLI.object_storage.credential:cli'), ('order', 'SoftLayer.CLI.order'), ('order:category-list', 'SoftLayer.CLI.order.category_list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py new file mode 100644 index 000000000..4bc3f4fc7 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -0,0 +1,39 @@ +credentialCreate = { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } +} + +getCredentials = [ + { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + }, + { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAFhDOjHqYJva", + "username": "XfHhBNBPlPdlWyaPPJAI", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + } +] diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index b25a457e1..2560d26c8 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -52,3 +52,47 @@ def list_endpoints(self): }) return endpoints + + def create_credential(self, identifier): + """Create object storage credential. + + :param int identifier: The object storage account identifier. + + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate', + id=identifier) + + def delete_credential(self, identifier, credential_id=None): + """Delete the object storage credential. + + :param int id: The object storage account identifier. + :param int credential_id: The credential id to be deleted. + + """ + credential = { + 'id': credential_id + } + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete', + credential, id=identifier) + + def limit_credential(self, identifier): + """Limit object storage credentials. + + :param int identifier: The object storage account identifier. + + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit', + id=identifier) + + def list_credential(self, identifier): + """List the object storage credentials. + + :param int identifier: The object storage account identifier. + + """ + + return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials', + id=identifier) diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 7c8e7ec96..28856a964 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -37,3 +37,73 @@ def test_list_endpoints(self): [{'datacenter': 'dal05', 'private': 'https://dal05/auth/v1.0/', 'public': 'https://dal05/auth/v1.0/'}]) + + def test_create_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') + accounts.return_value = { + "accountId": "12345", + "createDate": "2019-04-05T13:25:25-06:00", + "id": 11111, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCy", + "username": "XfHhBNBPlPdl", + "type": { + "description": "A credential for generating S3 Compatible Signatures.", + "keyName": "S3_COMPATIBLE_SIGNATURE", + "name": "S3 Compatible Signature" + } + } + + result = self.run_command(['object-storage', 'credential', 'create', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'id': 11111, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCy', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPdl'}] + ) + + def test_delete_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') + accounts.return_value = True + + result = self.run_command(['object-storage', 'credential', 'delete', '-id=100', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + 'The credential was deleted successful' + ) + + def test_limit_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') + accounts.return_value = 2 + + result = self.run_command(['object-storage', 'credential', 'limit', '100']) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{'limit': 2}]) + + def test_list_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') + accounts.return_value = [{'id': 1103123, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', + 'type': {'name': 'S3 Compatible Signature'}, + 'username': 'XfHhBNBPlPdlWya'}, + {'id': 1103333, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9', + 'type': {'name': 'S3 Compatible Signature'}, + 'username': 'XfHhBNBPlPd'}] + + result = self.run_command(['object-storage', 'credential', 'list', '100']) + + self.assert_no_fail(result) + print(json.loads(result.output)) + self.assertEqual(json.loads(result.output), + [{'id': 1103123, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPdlWya'}, + {'id': 1103333, + 'password': 'nwUEUsx6PiEoN0B1Xe9z9', + 'type_name': 'S3 Compatible Signature', + 'username': 'XfHhBNBPlPd'}]) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 478e5158b..c10524466 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -42,3 +42,80 @@ def test_list_endpoints_no_results(self): endpoints = self.object_storage.list_endpoints() self.assertEqual(endpoints, []) + + def test_create_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') + accounts.return_value = { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + credential = self.object_storage.create_credential(100) + self.assertEqual(credential, + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + }) + + def test_delete_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') + accounts.return_value = 'The credential was deleted successful' + + credential = self.object_storage.delete_credential(100) + self.assertEqual(credential, 'The credential was deleted successful') + + def test_limit_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') + accounts.return_value = 2 + + credential = self.object_storage.limit_credential(100) + self.assertEqual(credential, 2) + + def test_list_credential(self): + accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') + accounts.return_value = [ + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", + "username": "XfHhBNBPlPdlWyaP3fsd", + "type": { + "name": "S3 Compatible Signature" + } + }, + { + "id": 1102341, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + ] + credential = self.object_storage.list_credential(100) + self.assertEqual(credential, + [ + { + "id": 1103123, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", + "username": "XfHhBNBPlPdlWyaP3fsd", + "type": { + "name": "S3 Compatible Signature" + } + }, + { + "id": 1102341, + "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", + "username": "XfHhBNBPlPdlWyaP", + "type": { + "name": "S3 Compatible Signature" + } + } + ] + ) From 05ffff52edd22bb1d59ab18be8d4109d9450c313 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Apr 2019 12:18:18 -0400 Subject: [PATCH 0056/1582] Refactor object storage credentials. --- SoftLayer/CLI/object_storage/credential/delete.py | 2 +- SoftLayer/CLI/object_storage/credential/limit.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py index c5a9ab682..10d7dc655 100644 --- a/SoftLayer/CLI/object_storage/credential/delete.py +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -4,7 +4,7 @@ import click import SoftLayer -from SoftLayer.CLI import environment, exceptions +from SoftLayer.CLI import environment @click.command() diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py index 96d293eae..a82d414e8 100644 --- a/SoftLayer/CLI/object_storage/credential/limit.py +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -12,7 +12,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """ Credential limits for this IBM Cloud Object Storage account.""" + """Credential limits for this IBM Cloud Object Storage account.""" mgr = SoftLayer.ObjectStorageManager(env.client) limit = mgr.limit_credential(identifier) From 9479d91dac81b9fa1554a4c79dd820672af88d7c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Apr 2019 14:44:59 -0400 Subject: [PATCH 0057/1582] Refactor object storage credentials. --- SoftLayer/CLI/object_storage/credential/limit.py | 4 ++-- SoftLayer/CLI/object_storage/credential/list.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py index a82d414e8..cc3ad115c 100644 --- a/SoftLayer/CLI/object_storage/credential/limit.py +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -15,10 +15,10 @@ def cli(env, identifier): """Credential limits for this IBM Cloud Object Storage account.""" mgr = SoftLayer.ObjectStorageManager(env.client) - limit = mgr.limit_credential(identifier) + credential_limit = mgr.limit_credential(identifier) table = formatting.Table(['limit']) table.add_row([ - limit, + credential_limit, ]) env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/list.py b/SoftLayer/CLI/object_storage/credential/list.py index 2cf81ca3c..647e4224c 100644 --- a/SoftLayer/CLI/object_storage/credential/list.py +++ b/SoftLayer/CLI/object_storage/credential/list.py @@ -15,10 +15,10 @@ def cli(env, identifier): """Retrieve credentials used for generating an AWS signature. Max of 2.""" mgr = SoftLayer.ObjectStorageManager(env.client) - list = mgr.list_credential(identifier) + credential_list = mgr.list_credential(identifier) table = formatting.Table(['id', 'password', 'username', 'type_name']) - for credential in list: + for credential in credential_list: table.add_row([ credential['id'], credential['password'], From 2758e336183f06daa0e6f841fdd25f9bc4a6fea4 Mon Sep 17 00:00:00 2001 From: Albert Camacho Date: Tue, 9 Apr 2019 17:35:53 -0400 Subject: [PATCH 0058/1582] exception when there is no prices was refactored --- SoftLayer/CLI/subnet/create.py | 12 ++++-------- SoftLayer/managers/network.py | 4 ---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/subnet/create.py b/SoftLayer/CLI/subnet/create.py index 45c3f4619..1844cf627 100644 --- a/SoftLayer/CLI/subnet/create.py +++ b/SoftLayer/CLI/subnet/create.py @@ -42,14 +42,10 @@ def cli(env, network, quantity, vlan_id, ipv6, test): if ipv6: version = 6 - result = mgr.add_subnet(network, - quantity=quantity, - vlan_id=vlan_id, - version=version, - test_order=test) - if not result: - raise exceptions.CLIAbort( - 'Unable to place order: No valid price IDs found.') + try: + result = mgr.add_subnet(network, quantity=quantity, vlan_id=vlan_id, version=version, test_order=test) + except SoftLayer.SoftLayerAPIError: + raise exceptions.CLIAbort('There is no price id for {} {} ipv{}'.format(quantity, network, version)) table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4223143ae..4ecd76d3f 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -150,10 +150,6 @@ def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, price_id = item['prices'][0]['id'] break - if not price_id: - raise TypeError('Invalid combination specified for ordering a' - ' subnet.') - order = { 'packageId': 0, 'prices': [{'id': price_id}], From fe3c15ec846c91013648b95c72f6b2503f8b7607 Mon Sep 17 00:00:00 2001 From: "Albert J. Camacho" Date: Wed, 10 Apr 2019 11:55:20 -0400 Subject: [PATCH 0059/1582] #1129 unit tests --- tests/CLI/modules/subnet_tests.py | 57 +++++++++++++++++++++++++++++++ tests/managers/network_tests.py | 3 -- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 72825e0f9..9fa7d3925 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -4,12 +4,17 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing import json +import mock +import SoftLayer class SubnetTests(testing.TestCase): + def test_detail(self): result = self.run_command(['subnet', 'detail', '1234']) @@ -39,3 +44,55 @@ def test_detail(self): def test_list(self): result = self.run_command(['subnet', 'list']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_subnet_ipv4(self, confirm_mock): + confirm_mock.return_value = True + + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems + + place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + place_mock.return_value = SoftLayer_Product_Order.placeOrder + + result = self.run_command(['subnet', 'create', 'private', '8', '12346']) + self.assert_no_fail(result) + + output = [ + {'Item': 'this is a thing', 'cost': '2.00'}, + {'Item': 'Total monthly cost', 'cost': '2.00'} + ] + + self.assertEqual(output, json.loads(result.output)) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_subnet_ipv6(self, confirm_mock): + confirm_mock.return_value = True + + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems + + place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + place_mock.return_value = SoftLayer_Product_Order.placeOrder + + result = self.run_command(['subnet', 'create', '--v6', 'public', '64', '12346', '--test']) + self.assert_no_fail(result) + + output = [ + {'Item': 'this is a thing', 'cost': '2.00'}, + {'Item': 'Total monthly cost', 'cost': '2.00'} + ] + + self.assertEqual(output, json.loads(result.output)) + + def test_create_subnet_no_prices_found(self): + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems + + verify_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + verify_mock.side_effect = SoftLayer.SoftLayerAPIError('SoftLayer_Exception', 'Price not found') + + result = self.run_command(['subnet', 'create', '--v6', 'public', '32', '12346', '--test']) + + self.assertRaises(SoftLayer.SoftLayerAPIError, verify_mock) + self.assertEqual(result.exception.message, 'There is no price id for 32 public ipv6') diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 7a84bebae..223b7a3b5 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -24,9 +24,6 @@ def test_ip_lookup(self): 'getByIpAddress', args=('10.0.1.37',)) - def test_add_subnet_raises_exception_on_failure(self): - self.assertRaises(TypeError, self.network.add_subnet, ('bad')) - def test_add_global_ip(self): # Test a global IPv4 order result = self.network.add_global_ip(test_order=True) From 6d5b3f41d84194a6e8a06ea0333a9cd099beb9c4 Mon Sep 17 00:00:00 2001 From: "Albert J. Camacho" Date: Wed, 10 Apr 2019 12:22:40 -0400 Subject: [PATCH 0060/1582] #1129 fix unit tests --- tests/CLI/modules/subnet_tests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 9fa7d3925..47cb7e473 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -59,8 +59,7 @@ def test_create_subnet_ipv4(self, confirm_mock): self.assert_no_fail(result) output = [ - {'Item': 'this is a thing', 'cost': '2.00'}, - {'Item': 'Total monthly cost', 'cost': '2.00'} + {'Item': 'Total monthly cost', 'cost': '0.00'} ] self.assertEqual(output, json.loads(result.output)) @@ -72,8 +71,8 @@ def test_create_subnet_ipv6(self, confirm_mock): item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') item_mock.return_value = SoftLayer_Product_Package.getItems - place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') - place_mock.return_value = SoftLayer_Product_Order.placeOrder + place_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + place_mock.return_value = SoftLayer_Product_Order.verifyOrder result = self.run_command(['subnet', 'create', '--v6', 'public', '64', '12346', '--test']) self.assert_no_fail(result) From 8ffe12f63e172d1cc64d7f162cd39bf68f3141e2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 10 Apr 2019 17:47:06 -0500 Subject: [PATCH 0061/1582] #208 quote ordering support and doc updates --- SoftLayer/CLI/order/category_list.py | 9 +- SoftLayer/CLI/order/item_list.py | 19 +---- SoftLayer/CLI/order/package_list.py | 15 +--- SoftLayer/CLI/order/place.py | 9 +- SoftLayer/CLI/order/place_quote.py | 13 +-- SoftLayer/CLI/order/preset_list.py | 14 ++-- SoftLayer/CLI/order/quote.py | 120 +++++++++++++++++++++++++++ SoftLayer/CLI/order/quote_detail.py | 40 +++++++++ SoftLayer/CLI/order/quote_list.py | 43 ++++++++++ SoftLayer/CLI/routes.py | 24 +++--- SoftLayer/managers/ordering.py | 72 ++++++++-------- docs/cli/hardware.rst | 3 - docs/cli/ordering.rst | 53 ++++++++---- 13 files changed, 311 insertions(+), 123 deletions(-) create mode 100644 SoftLayer/CLI/order/quote.py create mode 100644 SoftLayer/CLI/order/quote_detail.py create mode 100644 SoftLayer/CLI/order/quote_list.py diff --git a/SoftLayer/CLI/order/category_list.py b/SoftLayer/CLI/order/category_list.py index 91671b8e0..9e0ea3a76 100644 --- a/SoftLayer/CLI/order/category_list.py +++ b/SoftLayer/CLI/order/category_list.py @@ -19,18 +19,11 @@ def cli(env, package_keyname, required): """List the categories of a package. - Package keynames can be retrieved from `slcli order package-list` + :: - \b - Example: # List the categories of Bare Metal servers slcli order category-list BARE_METAL_SERVER - When using the --required flag, it will list out only the categories - that are required for ordering that package (see `slcli order item-list`) - - \b - Example: # List the required categories for Bare Metal servers slcli order category-list BARE_METAL_SERVER --required diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index 92f83ceaf..ad9ae537f 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -18,29 +18,18 @@ def cli(env, package_keyname, keyword, category): """List package items used for ordering. - The items listed can be used with `slcli order place` to specify + The item keyNames listed can be used with `slcli order place` to specify the items that are being ordered in the package. - Package keynames can be retrieved using `slcli order package-list` - - \b - Note: + .. Note:: Items with a numbered category, like disk0 or gpu0, can be included multiple times in an order to match how many of the item you want to order. - \b - Example: + :: + # List all items in the VSI package slcli order item-list CLOUD_SERVER - The --keyword option is used to filter items by name. - - The --category option is used to filter items by category. - - Both --keyword and --category can be used together. - - \b - Example: # List Ubuntu OSes from the os category of the Bare Metal package slcli order item-list BARE_METAL_SERVER --category os --keyword ubuntu diff --git a/SoftLayer/CLI/order/package_list.py b/SoftLayer/CLI/order/package_list.py index 145ad0de1..917af56fe 100644 --- a/SoftLayer/CLI/order/package_list.py +++ b/SoftLayer/CLI/order/package_list.py @@ -20,23 +20,16 @@ def cli(env, keyword, package_type): """List packages that can be ordered via the placeOrder API. - \b - Example: - # List out all packages for ordering - slcli order package-list + :: - Keywords can also be used for some simple filtering functionality - to help find a package easier. + # List out all packages for ordering + slcli order package-list - \b - Example: # List out all packages with "server" in the name slcli order package-list --keyword server - Package types can be used to remove unwanted packages - \b - Example: + # Select only specifict package types slcli order package-list --package_type BARE_METAL_CPU """ manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index c6f6a129b..fd6d16a59 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -32,8 +32,8 @@ default='hourly', show_default=True, help="Billing rate") -@click.option('--complex-type', help=("The complex type of the order. This typically begins" - " with 'SoftLayer_Container_Product_Order_'.")) +@click.option('--complex-type', + help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")) @click.option('--extras', help="JSON string denoting extra data that needs to be sent with the order") @click.argument('order_items', nargs=-1) @@ -55,8 +55,9 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, items for the order, use `slcli order category-list`, and then provide the --category option for each category code in `slcli order item-list`. - \b - Example: + + Example:: + # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 slcli order place --billing hourly CLOUD_SERVER DALLAS13 \\ diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index c7bbe6265..90b6e1023 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -20,9 +20,9 @@ help="A custom name to be assigned to the quote (optional)") @click.option('--send-email', is_flag=True, - help="The quote will be sent to the email address associated.") -@click.option('--complex-type', help=("The complex type of the order. This typically begins" - " with 'SoftLayer_Container_Product_Order_'.")) + help="The quote will be sent to the email address associated with your user.") +@click.option('--complex-type', + help="The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.") @click.option('--extras', help="JSON string denoting extra data that needs to be sent with the order") @click.argument('order_items', nargs=-1) @@ -31,7 +31,7 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, extras, order_items): """Place a quote. - This CLI command is used for placing a quote of the specified package in + This CLI command is used for creating a quote of the specified package in the given location (denoted by a datacenter's long name). Orders made via the CLI can then be converted to be made programmatically by calling SoftLayer.OrderingManager.place_quote() with the same keynames. @@ -44,8 +44,9 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, items for the order, use `slcli order category-list`, and then provide the --category option for each category code in `slcli order item-list`. - \b - Example: + + Example:: + # Place quote a VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 slcli order place-quote --name "foobar" --send-email CLOUD_SERVER DALLAS13 \\ diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index a619caf77..2bb756250 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -20,19 +20,15 @@ def cli(env, package_keyname, keyword): """List package presets. - Package keynames can be retrieved from `slcli order package-list`. - Some packages do not have presets. + .. Note:: + Presets are set CPU / RAM / Disk allotments. You still need to specify required items. + Some packages do not have presets. + + :: - \b - Example: # List the presets for Bare Metal servers slcli order preset-list BARE_METAL_SERVER - The --keyword option can also be used for additional filtering on - the returned presets. - - \b - Example: # List the Bare Metal server presets that include a GPU slcli order preset-list BARE_METAL_SERVER --keyword gpu diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py new file mode 100644 index 000000000..c3027172c --- /dev/null +++ b/SoftLayer/CLI/order/quote.py @@ -0,0 +1,120 @@ +"""View and Order a quote""" +# :license: MIT, see LICENSE for more details. +import click +import json + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.managers import ordering +from SoftLayer.utils import lookup, clean_time + + + +from pprint import pprint as pp + + +def _parse_create_args(client, args): + """Converts CLI arguments to args for VSManager.create_instance. + + :param dict args: CLI arguments + """ + data = {} + + if args.get('quantity'): + data['quantity'] = int(args.get('quantity')) + if args.get('postinstall'): + data['provisionScripts'] = [args.get('postinstall')] + if args.get('complex_type'): + data['complexType'] = args.get('complex_type') + + if args.get('fqdn'): + servers = [] + for name in args.get('fqdn'): + fqdn = name.split(".", 1) + servers.append({'hostname': fqdn[0], 'domain': fqdn[1]}) + data['hardware'] = servers + + if args.get('image'): + if args.get('image').isdigit(): + image_mgr = SoftLayer.ImageManager(client) + image_details = image_mgr.get_image(args.get('image'), mask="id,globalIdentifier") + data['image_id'] = image_details['globalIdentifier'] + else: + data['image_id'] = args['image'] + + if args.get('userdata'): + data['userdata'] = args['userdata'] + elif args.get('userfile'): + with open(args['userfile'], 'r') as userfile: + data['userdata'] = userfile.read() + + # Get the SSH keys + if args.get('key'): + keys = [] + for key in args.get('key'): + resolver = SoftLayer.SshKeyManager(client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') + keys.append(key_id) + data['ssh_keys'] = keys + + + return data + + +@click.command() +@click.argument('quote') +@click.option('--verify', is_flag=True, default=False, show_default=True, + help="If specified, will only show what the quote will order, will NOT place an order") +@click.option('--quantity', type=int, default=None, + help="The quantity of the item being ordered if different from quoted value") +@click.option('--complex-type', default='SoftLayer_Container_Product_Order_Hardware_Server', show_default=True, + help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")) +@click.option('--userdata', '-u', help="User defined metadata string") +@click.option('--userfile', '-F', type=click.Path(exists=True, readable=True, resolve_path=True), + help="Read userdata from file") +@click.option('--postinstall', '-i', help="Post-install script to download") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@helpers.multi_option('--fqdn', required=True, + help=". formatted name to use. Specify one fqdn per server") +@click.option('--image', help="Image ID. See: 'slcli image list' for reference") +@environment.pass_env +def cli(env, quote, **args): + """View and Order a quote""" + table = formatting.Table([ + 'Id', 'Name', 'Created', 'Expiration', 'Status' + ]) + create_args = _parse_create_args(env.client, args) + + manager = ordering.OrderingManager(env.client) + quote_details = manager.get_quote_details(quote) + + package = quote_details['order']['items'][0]['package'] + create_args['packageId'] = package['id'] + + if args.get('verify'): + result = manager.verify_quote(quote, create_args) + verify_table = formatting.Table(['keyName', 'description', 'cost']) + verify_table.align['keyName'] = 'l' + verify_table.align['description'] = 'l' + for price in result['prices']: + cost_key = 'hourlyRecurringFee' if result['useHourlyPricing'] is True else 'recurringFee' + verify_table.add_row([ + price['item']['keyName'], + price['item']['description'], + price[cost_key] if cost_key in price else formatting.blank() + ]) + env.fout(verify_table) + else: + result = manager.order_quote(quote, create_args) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + table.add_row(['status', result ['placedOrder']['status']]) + env.fout(table) + + + diff --git a/SoftLayer/CLI/order/quote_detail.py b/SoftLayer/CLI/order/quote_detail.py new file mode 100644 index 000000000..b55976b68 --- /dev/null +++ b/SoftLayer/CLI/order/quote_detail.py @@ -0,0 +1,40 @@ +"""View a quote""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer.utils import lookup, clean_time + + +@click.command() +@click.argument('quote') +@environment.pass_env +def cli(env, quote): + """View a quote""" + + manager = ordering.OrderingManager(env.client) + result = manager.get_quote_details(quote) + + package = result['order']['items'][0]['package'] + title = "{} - Package: {}, Id {}".format(result.get('name'), package['keyName'], package['id']) + table = formatting.Table([ + 'Category', 'Description', 'Quantity', 'Recurring', 'One Time' + ], title=title) + table.align['Category'] = 'l' + table.align['Description'] = 'l' + + items = lookup(result, 'order', 'items') + for item in items: + table.add_row([ + item.get('categoryCode'), + item.get('description'), + item.get('quantity'), + item.get('recurringFee'), + item.get('oneTimeFee') + ]) + + env.fout(table) + + diff --git a/SoftLayer/CLI/order/quote_list.py b/SoftLayer/CLI/order/quote_list.py new file mode 100644 index 000000000..587ee579c --- /dev/null +++ b/SoftLayer/CLI/order/quote_list.py @@ -0,0 +1,43 @@ +"""List Quotes on an account.""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer.utils import clean_time + +from pprint import pprint as pp + +@click.command() +# @click.argument('package_keyname') +@click.option('--all', is_flag=True, default=False, + help="Show ALL quotes, by default only saved and pending quotes are shown") +@environment.pass_env +def cli(env, all): + """List all quotes on an account""" + table = formatting.Table([ + 'Id', 'Name', 'Created', 'Expiration', 'Status', 'Package Name', 'Package Id' + ]) + table.align['Name'] = 'l' + table.align['Package Name'] = 'r' + table.align['Package Id'] = 'l' + + manager = ordering.OrderingManager(env.client) + items = manager.get_quotes() + + + for item in items: + package = item['order']['items'][0]['package'] + table.add_row([ + item.get('id'), + item.get('name'), + clean_time(item.get('createDate')), + clean_time(item.get('modifyDate')), + item.get('status'), + package.get('keyName'), + package.get('id') + ]) + env.fout(table) + + diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c04233c52..dc7a3bcab 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -83,15 +83,13 @@ ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), ('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'), - ('block:replica-locations', - 'SoftLayer.CLI.block.replication.locations:cli'), + ('block:replica-locations', 'SoftLayer.CLI.block.replication.locations:cli'), ('block:snapshot-cancel', 'SoftLayer.CLI.block.snapshot.cancel:cli'), ('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-enable', 'SoftLayer.CLI.block.snapshot.enable:cli'), - ('block:snapshot-schedule-list', - 'SoftLayer.CLI.block.snapshot.schedule_list:cli'), + ('block:snapshot-schedule-list', 'SoftLayer.CLI.block.snapshot.schedule_list:cli'), ('block:snapshot-list', 'SoftLayer.CLI.block.snapshot.list:cli'), ('block:snapshot-order', 'SoftLayer.CLI.block.snapshot.order:cli'), ('block:snapshot-restore', 'SoftLayer.CLI.block.snapshot.restore:cli'), @@ -122,8 +120,7 @@ ('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-schedule-list', - 'SoftLayer.CLI.file.snapshot.schedule_list: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'), ('file:snapshot-restore', 'SoftLayer.CLI.file.snapshot.restore:cli'), @@ -164,10 +161,8 @@ ('ipsec:subnet-add', 'SoftLayer.CLI.vpn.ipsec.subnet.add:cli'), ('ipsec:subnet-remove', 'SoftLayer.CLI.vpn.ipsec.subnet.remove:cli'), ('ipsec:translation-add', 'SoftLayer.CLI.vpn.ipsec.translation.add:cli'), - ('ipsec:translation-remove', - 'SoftLayer.CLI.vpn.ipsec.translation.remove:cli'), - ('ipsec:translation-update', - 'SoftLayer.CLI.vpn.ipsec.translation.update:cli'), + ('ipsec:translation-remove', 'SoftLayer.CLI.vpn.ipsec.translation.remove:cli'), + ('ipsec:translation-update', 'SoftLayer.CLI.vpn.ipsec.translation.update:cli'), ('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'), ('loadbal', 'SoftLayer.CLI.loadbal'), @@ -195,10 +190,8 @@ ('nas:credentials', 'SoftLayer.CLI.nas.credentials:cli'), ('object-storage', 'SoftLayer.CLI.object_storage'), - ('object-storage:accounts', - 'SoftLayer.CLI.object_storage.list_accounts:cli'), - ('object-storage:endpoints', - 'SoftLayer.CLI.object_storage.list_endpoints:cli'), + ('object-storage:accounts', 'SoftLayer.CLI.object_storage.list_accounts:cli'), + ('object-storage:endpoints', 'SoftLayer.CLI.object_storage.list_endpoints:cli'), ('order', 'SoftLayer.CLI.order'), ('order:category-list', 'SoftLayer.CLI.order.category_list:cli'), @@ -208,6 +201,9 @@ ('order:preset-list', 'SoftLayer.CLI.order.preset_list:cli'), ('order:package-locations', 'SoftLayer.CLI.order.package_locations:cli'), ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), + ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), + ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), + ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 44f2fcab7..9c9c40f4d 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -11,6 +11,7 @@ from SoftLayer import exceptions + CATEGORY_MASK = '''id, isRequired, itemCategory[id, name, categoryCode] @@ -136,8 +137,8 @@ def get_quotes(self): :returns: a list of SoftLayer_Billing_Order_Quote """ - - quotes = self.client['Account'].getActiveQuotes() + mask = "mask[order[id,items[id,package[id,keyName]]]]" + quotes = self.client['Account'].getActiveQuotes(mask=mask) return quotes def get_quote_details(self, quote_id): @@ -146,7 +147,8 @@ def get_quote_details(self, quote_id): :param quote_id: ID number of target quote """ - quote = self.client['Billing_Order_Quote'].getObject(id=quote_id) + mask = "mask[order[id,items[package[id,keyName]]]]" + quote = self.client['Billing_Order_Quote'].getObject(id=quote_id, mask=mask) return quote def get_order_container(self, quote_id): @@ -157,62 +159,62 @@ def get_order_container(self, quote_id): quote = self.client['Billing_Order_Quote'] container = quote.getRecalculatedOrderContainer(id=quote_id) - return container['orderContainers'][0] + return container - def generate_order_template(self, quote_id, extra, quantity=1): + def generate_order_template(self, quote_id, extra): """Generate a complete order template. :param int quote_id: ID of target quote - :param list extra: List of dictionaries that have extra details about - the order such as hostname or domain names for - virtual servers or hardware nodes - :param int quantity: Number of ~things~ to order + :param dictionary extra: Overrides for the defaults of SoftLayer_Container_Product_Order """ container = self.get_order_container(quote_id) - container['quantity'] = quantity - - # NOTE(kmcdonald): This will only work with virtualGuests and hardware. - # There has to be a better way, since this is based on - # an existing quote that supposedly knows about this - # detail - if container['packageId'] == 46: - product_type = 'virtualGuests' - else: - product_type = 'hardware' - if len(extra) != quantity: - raise ValueError("You must specify extra for each server in the quote") + for key in extra.keys(): + container[key] = extra[key] - container[product_type] = [] - for extra_details in extra: - container[product_type].append(extra_details) - container['presetId'] = None return container - def verify_quote(self, quote_id, extra, quantity=1): + def verify_quote(self, quote_id, extra): """Verifies that a quote order is valid. + :: + + extras = { + 'hardware': {'hostname': 'test', 'domain': 'testing.com'}, + 'quantity': 2 + } + manager = ordering.OrderingManager(env.client) + result = manager.verify_quote(12345, extras) + + :param int quote_id: ID for the target quote - :param list hostnames: hostnames of the servers - :param string domain: domain of the new servers + :param dictionary extra: Overrides for the defaults of SoftLayer_Container_Product_Order :param int quantity: Quantity to override default """ + container = self.generate_order_template(quote_id, extra) - container = self.generate_order_template(quote_id, extra, quantity=quantity) - return self.order_svc.verifyOrder(container) + return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', container, id=quote_id) - def order_quote(self, quote_id, extra, quantity=1): + def order_quote(self, quote_id, extra): """Places an order using a quote + :: + + extras = { + 'hardware': {'hostname': 'test', 'domain': 'testing.com'}, + 'quantity': 2 + } + manager = ordering.OrderingManager(env.client) + result = manager.order_quote(12345, extras) + :param int quote_id: ID for the target quote - :param list hostnames: hostnames of the servers - :param string domain: domain of the new server + :param dictionary extra: Overrides for the defaults of SoftLayer_Container_Product_Order :param int quantity: Quantity to override default """ - container = self.generate_order_template(quote_id, extra, quantity=quantity) - return self.order_svc.placeOrder(container) + container = self.generate_order_template(quote_id, extra) + return self.client.call('SoftLayer_Billing_Order_Quote','placeOrder', container, id=quote_id) def get_package_by_key(self, package_keyname, mask=None): """Get a single package with a given key. diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 5b3f480b1..f6bb7e488 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -86,9 +86,6 @@ This function updates the firmware of a server. If already at the latest version :show-nested: - :show-nested: - - .. click:: SoftLayer.CLI.hardware.ready:cli :prog: hw ready :show-nested: diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 0724cb60f..3f2eed717 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -11,11 +11,14 @@ The basic flow for ordering goes something like this... #. item-list #. place -.. _cli_ordering_package_list: -order package-list ------------------- -This command will list all of the packages that are available to be ordered. This is the starting point for placing any order. Find the package keyName you want to order, and use it for the next steps. + + + +.. click:: SoftLayer.CLI.order.package_list:cli + :prog: order package-list + :show-nested: + .. note:: * CLOUD_SERVER: These are Virtual Servers @@ -27,10 +30,15 @@ This command will list all of the packages that are available to be ordered. Thi Bluemix services listed here may still need to be ordered through the Bluemix CLI/Portal -.. _cli_ordering_category_list: -order category-list -------------------- +.. click:: SoftLayer.CLI.order.package_locations:cli + :prog: order package-locations + :show-nested: + +.. click:: SoftLayer.CLI.order.category_list:cli + :prog: order category-list + :show-nested: + Shows all the available categories for a certain package, useful in finding the required categories. Categories that are required will need to have a corresponding item included with any orders These are all the required categories for ``BARE_METAL_SERVER`` @@ -52,23 +60,22 @@ These are all the required categories for ``BARE_METAL_SERVER`` : VPN Management - Private Network : vpn_management : Y : :........................................:.......................:............: -.. _cli_ordering_item_list: +.. click:: SoftLayer.CLI.order.item_list:cli + :prog: order item-list + :show-nested: -order item-list ---------------- Shows all the prices for a given package. Collect all the items you want included on your server. Don't forget to include the required category items. If forgotten, ``order place`` will tell you about it. -.. _cli_ordering_preset_list: +.. click:: SoftLayer.CLI.order.preset_list:cli + :prog: order preset-list + :show-nested: -order preset-list ------------------ -Some packages have presets which makes ordering significantly simpler. These will have set CPU / RAM / Disk allotments. You still need to specify required items -.. _cli_ordering_place: +.. click:: SoftLayer.CLI.order.place:cli + :prog: order place + :show-nested: -order place ------------ Now that you have the package you want, the prices needed, and found a location, it is time to place an order. order place @@ -85,6 +92,7 @@ order place --extras '{"hardware": [{"hostname" : "testOrder", "domain": "cgallo.com"}]}' \ --complex-type SoftLayer_Container_Product_Order_Hardware_Server + order place ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -105,4 +113,13 @@ order place UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \ NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \ --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \ - --complex-type SoftLayer_Container_Product_Order_Virtual_Guest \ No newline at end of file + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + + +.. click:: SoftLayer.CLI.order.quote_list:cli + :prog: order quote-list + :show-nested: + +.. click:: SoftLayer.CLI.order.place_quote:cli + :prog: order place-quote + :show-nested: From 65383f8d82004dba211ac768ff6b6bec94a7d10c Mon Sep 17 00:00:00 2001 From: rodrabe Date: Fri, 12 Apr 2019 15:00:10 -0500 Subject: [PATCH 0062/1582] Change encrypt parameters for importing of images. --- SoftLayer/CLI/image/import.py | 14 +++++--------- SoftLayer/managers/image.py | 12 +++++------- tests/managers/image_tests.py | 4 ++-- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 7f1b1e83e..bcf58c003 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -25,17 +25,14 @@ "creating this key see https://console.bluemix.net/docs/" "services/cloud-object-storage/iam/users-serviceids.html" "#serviceidapikeys") -@click.option('--root-key-id', +@click.option('--root-key-crn', default=None, - help="ID of the root key in Key Protect") + help="CRN of the root key in your KMS instance") @click.option('--wrapped-dek', default=None, help="Wrapped Data Encryption Key provided by IBM KeyProtect. " "For more info see https://console.bluemix.net/docs/" "services/key-protect/wrap-keys.html#wrap-keys") -@click.option('--kp-id', - default=None, - help="ID of the IBM Key Protect Instance") @click.option('--cloud-init', is_flag=True, help="Specifies if image is cloud-init") @@ -46,8 +43,8 @@ is_flag=True, help="Specifies if image is encrypted") @environment.pass_env -def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, - kp_id, cloud_init, byol, is_encrypted): +def cli(env, name, note, os_code, uri, ibm_api_key, root_key_crn, wrapped_dek, + cloud_init, byol, is_encrypted): """Import an image. The URI for an object storage object (.vhd/.iso file) of the format: @@ -63,9 +60,8 @@ def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, os_code=os_code, uri=uri, ibm_api_key=ibm_api_key, - root_key_id=root_key_id, + crkCrn=root_key_crn, wrapped_dek=wrapped_dek, - kp_id=kp_id, cloud_init=cloud_init, byol=byol, is_encrypted=is_encrypted diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 2eb4bc982..55162ed68 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -121,8 +121,8 @@ def edit(self, image_id, name=None, note=None, tag=None): return bool(name or note or tag) def import_image_from_uri(self, name, uri, os_code=None, note=None, - ibm_api_key=None, root_key_id=None, - wrapped_dek=None, kp_id=None, cloud_init=False, + ibm_api_key=None, root_key_crn=None, + wrapped_dek=None, cloud_init=False, byol=False, is_encrypted=False): """Import a new image from object storage. @@ -137,10 +137,9 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, :param string note: Note to add to the image :param string ibm_api_key: Ibm Api Key needed to communicate with ICOS and Key Protect - :param string root_key_id: ID of the root key in Key Protect + :param string root_key_crn: CRN of the root key in your KMS :param string wrapped_dek: Wrapped Data Encryption Key provided by - IBM KeyProtect - :param string kp_id: ID of the IBM Key Protect Instance + your KMS :param boolean cloud_init: Specifies if image is cloud-init :param boolean byol: Specifies if image is bring your own license :param boolean is_encrypted: Specifies if image is encrypted @@ -152,9 +151,8 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, 'operatingSystemReferenceCode': os_code, 'uri': uri, 'ibmApiKey': ibm_api_key, - 'rootKeyId': root_key_id, + 'crkCrn': root_key_crn, 'wrappedDek': wrapped_dek, - 'keyProtectId': kp_id, 'cloudInit': cloud_init, 'byol': byol, 'isEncrypted': is_encrypted diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index 50a081988..615588723 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -151,7 +151,7 @@ def test_import_image_cos(self): uri='cos://some_uri', os_code='UBUNTU_LATEST', ibm_api_key='some_ibm_key', - root_key_id='some_root_key_id', + root_key_crn='some_root_key_crn', wrapped_dek='some_dek', kp_id='some_id', cloud_init=False, @@ -167,7 +167,7 @@ def test_import_image_cos(self): 'operatingSystemReferenceCode': 'UBUNTU_LATEST', 'uri': 'cos://some_uri', 'ibmApiKey': 'some_ibm_key', - 'rootKeyId': 'some_root_key_id', + 'crkCrn': 'some_root_key_crn', 'wrappedDek': 'some_dek', 'keyProtectId': 'some_id', 'cloudInit': False, From 0f60878909ecf1306afa676c202698a139aa03f4 Mon Sep 17 00:00:00 2001 From: rodrabe Date: Fri, 12 Apr 2019 15:00:10 -0500 Subject: [PATCH 0063/1582] Change encrypt parameters for importing of images. --- SoftLayer/CLI/image/import.py | 14 +++++--------- SoftLayer/managers/image.py | 12 +++++------- tests/managers/image_tests.py | 6 ++---- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 7f1b1e83e..bcf58c003 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -25,17 +25,14 @@ "creating this key see https://console.bluemix.net/docs/" "services/cloud-object-storage/iam/users-serviceids.html" "#serviceidapikeys") -@click.option('--root-key-id', +@click.option('--root-key-crn', default=None, - help="ID of the root key in Key Protect") + help="CRN of the root key in your KMS instance") @click.option('--wrapped-dek', default=None, help="Wrapped Data Encryption Key provided by IBM KeyProtect. " "For more info see https://console.bluemix.net/docs/" "services/key-protect/wrap-keys.html#wrap-keys") -@click.option('--kp-id', - default=None, - help="ID of the IBM Key Protect Instance") @click.option('--cloud-init', is_flag=True, help="Specifies if image is cloud-init") @@ -46,8 +43,8 @@ is_flag=True, help="Specifies if image is encrypted") @environment.pass_env -def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, - kp_id, cloud_init, byol, is_encrypted): +def cli(env, name, note, os_code, uri, ibm_api_key, root_key_crn, wrapped_dek, + cloud_init, byol, is_encrypted): """Import an image. The URI for an object storage object (.vhd/.iso file) of the format: @@ -63,9 +60,8 @@ def cli(env, name, note, os_code, uri, ibm_api_key, root_key_id, wrapped_dek, os_code=os_code, uri=uri, ibm_api_key=ibm_api_key, - root_key_id=root_key_id, + crkCrn=root_key_crn, wrapped_dek=wrapped_dek, - kp_id=kp_id, cloud_init=cloud_init, byol=byol, is_encrypted=is_encrypted diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 2eb4bc982..55162ed68 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -121,8 +121,8 @@ def edit(self, image_id, name=None, note=None, tag=None): return bool(name or note or tag) def import_image_from_uri(self, name, uri, os_code=None, note=None, - ibm_api_key=None, root_key_id=None, - wrapped_dek=None, kp_id=None, cloud_init=False, + ibm_api_key=None, root_key_crn=None, + wrapped_dek=None, cloud_init=False, byol=False, is_encrypted=False): """Import a new image from object storage. @@ -137,10 +137,9 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, :param string note: Note to add to the image :param string ibm_api_key: Ibm Api Key needed to communicate with ICOS and Key Protect - :param string root_key_id: ID of the root key in Key Protect + :param string root_key_crn: CRN of the root key in your KMS :param string wrapped_dek: Wrapped Data Encryption Key provided by - IBM KeyProtect - :param string kp_id: ID of the IBM Key Protect Instance + your KMS :param boolean cloud_init: Specifies if image is cloud-init :param boolean byol: Specifies if image is bring your own license :param boolean is_encrypted: Specifies if image is encrypted @@ -152,9 +151,8 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, 'operatingSystemReferenceCode': os_code, 'uri': uri, 'ibmApiKey': ibm_api_key, - 'rootKeyId': root_key_id, + 'crkCrn': root_key_crn, 'wrappedDek': wrapped_dek, - 'keyProtectId': kp_id, 'cloudInit': cloud_init, 'byol': byol, 'isEncrypted': is_encrypted diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index 50a081988..b36deea75 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -151,9 +151,8 @@ def test_import_image_cos(self): uri='cos://some_uri', os_code='UBUNTU_LATEST', ibm_api_key='some_ibm_key', - root_key_id='some_root_key_id', + root_key_crn='some_root_key_crn', wrapped_dek='some_dek', - kp_id='some_id', cloud_init=False, byol=False, is_encrypted=False @@ -167,9 +166,8 @@ def test_import_image_cos(self): 'operatingSystemReferenceCode': 'UBUNTU_LATEST', 'uri': 'cos://some_uri', 'ibmApiKey': 'some_ibm_key', - 'rootKeyId': 'some_root_key_id', + 'crkCrn': 'some_root_key_crn', 'wrappedDek': 'some_dek', - 'keyProtectId': 'some_id', 'cloudInit': False, 'byol': False, 'isEncrypted': False From aff70482263e974d764c29f1290c18342ce3e888 Mon Sep 17 00:00:00 2001 From: rodrabe Date: Mon, 15 Apr 2019 10:36:52 -0500 Subject: [PATCH 0064/1582] Merge remote-tracking branch 'origin/master' --- SoftLayer/CLI/image/import.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index bcf58c003..53082c9ac 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -60,7 +60,7 @@ def cli(env, name, note, os_code, uri, ibm_api_key, root_key_crn, wrapped_dek, os_code=os_code, uri=uri, ibm_api_key=ibm_api_key, - crkCrn=root_key_crn, + root_key_crn=root_key_crn, wrapped_dek=wrapped_dek, cloud_init=cloud_init, byol=byol, diff --git a/setup.py b/setup.py index abe08c52e..12835570f 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.7.2', + version='5.7.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From a00df24256e5b94854200d9ebce9fb6cd7463821 Mon Sep 17 00:00:00 2001 From: rodrabe Date: Mon, 15 Apr 2019 14:57:21 -0500 Subject: [PATCH 0065/1582] Modify comments --- SoftLayer/managers/image.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 55162ed68..34efb36bf 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -136,10 +136,14 @@ def import_image_from_uri(self, name, uri, os_code=None, note=None, :param string os_code: The reference code of the operating system :param string note: Note to add to the image :param string ibm_api_key: Ibm Api Key needed to communicate with ICOS - and Key Protect - :param string root_key_crn: CRN of the root key in your KMS + and your KMS + :param string root_key_crn: CRN of the root key in your KMS. Go to your + KMS (Key Protect or Hyper Protect) provider to get the CRN for your + root key. An example CRN: + crn:v1:bluemix:public:hs-crypto:us-south:acctID:serviceID:key:keyID' + Used only when is_encrypted is True. :param string wrapped_dek: Wrapped Data Encryption Key provided by - your KMS + your KMS. Used only when is_encrypted is True. :param boolean cloud_init: Specifies if image is cloud-init :param boolean byol: Specifies if image is bring your own license :param boolean is_encrypted: Specifies if image is encrypted From 7a3c644cbaf3fdf5a267be3fffb60cc77abf0ed9 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 17 Apr 2019 18:33:57 -0500 Subject: [PATCH 0066/1582] finishing up quote ordering stuff --- SoftLayer/CLI/order/place.py | 4 +- SoftLayer/CLI/order/place_quote.py | 2 +- SoftLayer/CLI/order/preset_list.py | 2 +- SoftLayer/CLI/order/quote.py | 33 +++--- SoftLayer/CLI/order/quote_detail.py | 4 +- SoftLayer/CLI/order/quote_list.py | 13 +-- SoftLayer/fixtures/SoftLayer_Account.py | 29 +++++ .../fixtures/SoftLayer_Billing_Order_Quote.py | 99 +++++++++++++++-- SoftLayer/managers/ordering.py | 9 +- tests/CLI/modules/order_tests.py | 104 ++++++++++++++++++ tests/managers/ordering_tests.py | 58 ++++++---- 11 files changed, 289 insertions(+), 68 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index fd6d16a59..6b66fb110 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -32,7 +32,7 @@ default='hourly', show_default=True, help="Billing rate") -@click.option('--complex-type', +@click.option('--complex-type', help=("The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.")) @click.option('--extras', help="JSON string denoting extra data that needs to be sent with the order") @@ -57,7 +57,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, Example:: - + # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 slcli order place --billing hourly CLOUD_SERVER DALLAS13 \\ diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index 90b6e1023..28865ff70 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -21,7 +21,7 @@ @click.option('--send-email', is_flag=True, help="The quote will be sent to the email address associated with your user.") -@click.option('--complex-type', +@click.option('--complex-type', help="The complex type of the order. Starts with 'SoftLayer_Container_Product_Order'.") @click.option('--extras', help="JSON string denoting extra data that needs to be sent with the order") diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index 2bb756250..7397f9428 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -20,7 +20,7 @@ def cli(env, package_keyname, keyword): """List package presets. - .. Note:: + .. Note:: Presets are set CPU / RAM / Disk allotments. You still need to specify required items. Some packages do not have presets. diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index c3027172c..7f058bf44 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -1,18 +1,13 @@ """View and Order a quote""" # :license: MIT, see LICENSE for more details. import click -import json from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer.managers import ImageManager as ImageManager from SoftLayer.managers import ordering -from SoftLayer.utils import lookup, clean_time - - - -from pprint import pprint as pp +from SoftLayer.managers import SshKeyManager as SshKeyManager def _parse_create_args(client, args): @@ -38,27 +33,30 @@ def _parse_create_args(client, args): if args.get('image'): if args.get('image').isdigit(): - image_mgr = SoftLayer.ImageManager(client) + image_mgr = ImageManager(client) image_details = image_mgr.get_image(args.get('image'), mask="id,globalIdentifier") - data['image_id'] = image_details['globalIdentifier'] + data['imageTemplateGlobalIdentifier'] = image_details['globalIdentifier'] else: - data['image_id'] = args['image'] + data['imageTemplateGlobalIdentifier'] = args['image'] + userdata = None if args.get('userdata'): - data['userdata'] = args['userdata'] + userdata = args['userdata'] elif args.get('userfile'): with open(args['userfile'], 'r') as userfile: - data['userdata'] = userfile.read() + userdata = userfile.read() + if userdata: + for hardware in data['hardware']: + hardware['userData'] = [{'value': userdata}] # Get the SSH keys if args.get('key'): keys = [] for key in args.get('key'): - resolver = SoftLayer.SshKeyManager(client).resolve_ids + resolver = SshKeyManager(client).resolve_ids key_id = helpers.resolve_id(resolver, key, 'SshKey') keys.append(key_id) - data['ssh_keys'] = keys - + data['sshKeys'] = keys return data @@ -113,8 +111,5 @@ def cli(env, quote, **args): table.align['value'] = 'l' table.add_row(['id', result['orderId']]) table.add_row(['created', result['orderDate']]) - table.add_row(['status', result ['placedOrder']['status']]) + table.add_row(['status', result['placedOrder']['status']]) env.fout(table) - - - diff --git a/SoftLayer/CLI/order/quote_detail.py b/SoftLayer/CLI/order/quote_detail.py index b55976b68..cef5f41e5 100644 --- a/SoftLayer/CLI/order/quote_detail.py +++ b/SoftLayer/CLI/order/quote_detail.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers import ordering -from SoftLayer.utils import lookup, clean_time +from SoftLayer.utils import lookup @click.command() @@ -36,5 +36,3 @@ def cli(env, quote): ]) env.fout(table) - - diff --git a/SoftLayer/CLI/order/quote_list.py b/SoftLayer/CLI/order/quote_list.py index 587ee579c..c43ff7542 100644 --- a/SoftLayer/CLI/order/quote_list.py +++ b/SoftLayer/CLI/order/quote_list.py @@ -1,4 +1,4 @@ -"""List Quotes on an account.""" +"""List active quotes on an account.""" # :license: MIT, see LICENSE for more details. import click @@ -7,15 +7,11 @@ from SoftLayer.managers import ordering from SoftLayer.utils import clean_time -from pprint import pprint as pp @click.command() -# @click.argument('package_keyname') -@click.option('--all', is_flag=True, default=False, - help="Show ALL quotes, by default only saved and pending quotes are shown") @environment.pass_env -def cli(env, all): - """List all quotes on an account""" +def cli(env): + """List all active quotes on an account""" table = formatting.Table([ 'Id', 'Name', 'Created', 'Expiration', 'Status', 'Package Name', 'Package Id' ]) @@ -26,7 +22,6 @@ def cli(env, all): manager = ordering.OrderingManager(env.client) items = manager.get_quotes() - for item in items: package = item['order']['items'][0]['package'] table.add_row([ @@ -39,5 +34,3 @@ def cli(env, all): package.get('id') ]) env.fout(table) - - diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ffe556dee..cf884aefd 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -489,9 +489,38 @@ }] getActiveQuotes = [{ + 'accountId': 1234, 'id': 1234, 'name': 'TestQuote1234', 'quoteKey': '1234test4321', + 'createDate': '2019-04-10T14:26:03-06:00', + 'modifyDate': '2019-04-10T14:26:03-06:00', + 'order': { + 'id': 37623333, + 'items': [ + { + 'categoryCode': 'guest_core', + 'description': '4 x 2.0 GHz or higher Cores', + 'id': 468394713, + 'itemId': 859, + 'itemPriceId': '1642', + 'oneTimeAfterTaxAmount': '0', + 'oneTimeFee': '0', + 'oneTimeFeeTaxRate': '0', + 'oneTimeTaxAmount': '0', + 'quantity': 1, + 'recurringAfterTaxAmount': '0', + 'recurringFee': '0', + 'recurringTaxAmount': '0', + 'setupAfterTaxAmount': '0', + 'setupFee': '0', + 'setupFeeDeferralMonths': None, + 'setupFeeTaxRate': '0', + 'setupTaxAmount': '0', + 'package': {'id': 46, 'keyName': 'CLOUD_SERVER'} + }, + ] + } }] getOrders = [{ diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py index 6302bfa94..d051d6f86 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py @@ -3,16 +3,101 @@ 'id': 1234, 'name': 'TestQuote1234', 'quoteKey': '1234test4321', + 'order': { + 'id': 37623333, + 'items': [ + { + 'categoryCode': 'guest_core', + 'description': '4 x 2.0 GHz or higher Cores', + 'id': 468394713, + 'itemId': 859, + 'itemPriceId': '1642', + 'oneTimeAfterTaxAmount': '0', + 'oneTimeFee': '0', + 'oneTimeFeeTaxRate': '0', + 'oneTimeTaxAmount': '0', + 'quantity': 1, + 'recurringAfterTaxAmount': '0', + 'recurringFee': '0', + 'recurringTaxAmount': '0', + 'setupAfterTaxAmount': '0', + 'setupFee': '0', + 'setupFeeDeferralMonths': None, + 'setupFeeTaxRate': '0', + 'setupTaxAmount': '0', + 'package': {'id': 46, 'keyName': 'CLOUD_SERVER'} + }, + ] + } } getRecalculatedOrderContainer = { - 'orderContainers': [{ - 'presetId': '', + 'presetId': '', + 'prices': [{ + 'id': 1921 + }], + 'quantity': 1, + 'packageId': 50, + 'useHourlyPricing': '', + +} + +verifyOrder = { + 'orderId': 1234, + 'orderDate': '2013-08-01 15:23:45', + 'useHourlyPricing': False, + 'prices': [{ + 'id': 1, + 'laborFee': '2', + 'oneTimeFee': '2', + 'oneTimeFeeTax': '.1', + 'quantity': 1, + 'recurringFee': '2', + 'recurringFeeTax': '.1', + 'hourlyRecurringFee': '2', + 'setupFee': '1', + 'item': {'id': 1, 'description': 'this is a thing', 'keyName': 'TheThing'}, + }]} + +placeOrder = { + 'orderId': 1234, + 'orderDate': '2013-08-01 15:23:45', + 'orderDetails': { 'prices': [{ - 'id': 1921 + 'id': 1, + 'laborFee': '2', + 'oneTimeFee': '2', + 'oneTimeFeeTax': '.1', + 'quantity': 1, + 'recurringFee': '2', + 'recurringFeeTax': '.1', + 'hourlyRecurringFee': '2', + 'setupFee': '1', + 'item': {'id': 1, 'description': 'this is a thing'}, }], - 'quantity': 1, - 'packageId': 50, - 'useHourlyPricing': '', - }], + 'virtualGuests': [{ + 'id': 1234567, + 'globalIdentifier': '1a2b3c-1701', + 'fullyQualifiedDomainName': 'test.guest.com' + }], + }, + 'placedOrder': { + 'id': 37985543, + 'orderQuoteId': 2639077, + 'orderTypeId': 4, + 'status': 'PENDING_AUTO_APPROVAL', + 'items': [ + { + 'categoryCode': 'guest_core', + 'description': '4 x 2.0 GHz or higher Cores', + 'id': 472527133, + 'itemId': 859, + 'itemPriceId': '1642', + 'laborFee': '0', + 'oneTimeFee': '0', + 'recurringFee': '0', + 'setupFee': '0', + } + ] + } } diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 9c9c40f4d..ccd490b0a 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -161,15 +161,20 @@ def get_order_container(self, quote_id): container = quote.getRecalculatedOrderContainer(id=quote_id) return container - def generate_order_template(self, quote_id, extra): + def generate_order_template(self, quote_id, extra, quantity=1): """Generate a complete order template. :param int quote_id: ID of target quote :param dictionary extra: Overrides for the defaults of SoftLayer_Container_Product_Order + :param int quantity: Number of items to order. """ + if not isinstance(extra, dict): + raise ValueError("extra is not formatted properly") + container = self.get_order_container(quote_id) + container['quantity'] = quantity for key in extra.keys(): container[key] = extra[key] @@ -214,7 +219,7 @@ def order_quote(self, quote_id, extra): """ container = self.generate_order_template(quote_id, extra) - return self.client.call('SoftLayer_Billing_Order_Quote','placeOrder', container, id=quote_id) + return self.client.call('SoftLayer_Billing_Order_Quote', 'placeOrder', container, id=quote_id) def get_package_by_key(self, package_keyname, mask=None): """Get a single package with a given key. diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 02141e808..a82b731fc 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ import json +import sys +import tempfile from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -252,6 +254,12 @@ def test_preset_list(self): 'description': 'description3'}], json.loads(result.output)) + def test_preset_list_keywork(self): + result = self.run_command(['order', 'preset-list', 'package', '--keyword', 'testKeyWord']) + _filter = {'activePresets': {'name': {'operation': '*= testKeyWord'}}} + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Package', 'getActivePresets', filter=_filter) + def test_location_list(self): result = self.run_command(['order', 'package-locations', 'package']) self.assert_no_fail(result) @@ -262,6 +270,102 @@ def test_location_list(self): print(result.output) self.assertEqual(expected_results, json.loads(result.output)) + def test_quote_verify(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + + def test_quote_verify_image(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--image', '1234', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Virtual_Guest_Block_Device_Template_Group', 'getObject', identifier='1234') + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual('0B5DEAF4-643D-46CA-A695-CECBE8832C9D', verify_args['imageTemplateGlobalIdentifier']) + + def test_quote_verify_image_guid(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--image', + '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual('0B5DEAF4-643D-46CA-A695-CECBE8832C9D', verify_args['imageTemplateGlobalIdentifier']) + + def test_quote_verify_userdata(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--userdata', 'aaaa1234', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual([{'value': 'aaaa1234'}], verify_args['hardware'][0]['userData']) + + def test_quote_verify_userdata_file(self): + if (sys.platform.startswith("win")): + self.skipTest("TempFile tests doesn't work in Windows") + with tempfile.NamedTemporaryFile() as userfile: + userfile.write(b"some data") + userfile.flush() + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--userfile', userfile.name, + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual([{'value': 'some data'}], verify_args['hardware'][0]['userData']) + + def test_quote_verify_sshkey(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--key', 'Test 1', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + + self.assert_called_with('SoftLayer_Account', 'getSshKeys') + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual(['100'], verify_args['sshKeys']) + + def test_quote_verify_postinstall_others(self): + result = self.run_command([ + 'order', 'quote', '12345', '--verify', '--fqdn', 'test01.test.com', '--quantity', '2', + '--postinstall', 'https://127.0.0.1/test.sh', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder', identifier='12345') + verify_call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder') + verify_args = getattr(verify_call[0], 'args')[0] + self.assertEqual(['https://127.0.0.1/test.sh'], verify_args['provisionScripts']) + self.assertEqual(2, verify_args['quantity']) + + def test_quote_place(self): + result = self.run_command([ + 'order', 'quote', '12345', '--fqdn', 'test01.test.com', + '--complex-type', 'SoftLayer_Container_Product_Order_Virtual_Guest']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'placeOrder', identifier='12345') + + def test_quote_detail(self): + result = self.run_command(['order', 'quote-detail', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getObject', identifier='12345') + + def test_quote_list(self): + result = self.run_command(['order', 'quote-list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getActiveQuotes') + def _get_order_items(self): item1 = {'keyName': 'ITEM1', 'description': 'description1', 'itemCategory': {'categoryCode': 'cat1'}, diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 66eb2f405..665be1c70 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -90,9 +90,8 @@ def test_get_package_id_by_type_fails_for_nonexistent_package_type(self): def test_get_order_container(self): container = self.ordering.get_order_container(1234) - quote = self.ordering.client['Billing_Order_Quote'] - container_fixture = quote.getRecalculatedOrderContainer(id=1234) - self.assertEqual(container, container_fixture['orderContainers'][0]) + self.assertEqual(1, container['quantity']) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getRecalculatedOrderContainer') def test_get_quotes(self): quotes = self.ordering.get_quotes() @@ -106,13 +105,16 @@ def test_get_quote_details(self): self.assertEqual(quote, quote_fixture) def test_verify_quote(self): - result = self.ordering.verify_quote(1234, - [{'hostname': 'test1', - 'domain': 'example.com'}], - quantity=1) + extras = { + 'hardware': [{ + 'hostname': 'test1', + 'domain': 'example.com' + }] + } + result = self.ordering.verify_quote(1234, extras) - self.assertEqual(result, fixtures.SoftLayer_Product_Order.verifyOrder) - self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') + self.assertEqual(result, fixtures.SoftLayer_Billing_Order_Quote.verifyOrder) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder') def test_order_quote_virtual_guest(self): guest_quote = { @@ -126,21 +128,24 @@ def test_order_quote_virtual_guest(self): 'useHourlyPricing': '', }], } - + extras = { + 'hardware': [{ + 'hostname': 'test1', + 'domain': 'example.com' + }] + } mock = self.set_mock('SoftLayer_Billing_Order_Quote', 'getRecalculatedOrderContainer') mock.return_value = guest_quote - result = self.ordering.order_quote(1234, - [{'hostname': 'test1', - 'domain': 'example.com'}], - quantity=1) + result = self.ordering.order_quote(1234, extras) - self.assertEqual(result, fixtures.SoftLayer_Product_Order.placeOrder) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + self.assertEqual(result, fixtures.SoftLayer_Billing_Order_Quote.placeOrder) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'placeOrder') def test_generate_order_template(self): - result = self.ordering.generate_order_template( - 1234, [{'hostname': 'test1', 'domain': 'example.com'}], quantity=1) - self.assertEqual(result, {'presetId': None, + extras = {'hardware': [{'hostname': 'test1', 'domain': 'example.com'}]} + + result = self.ordering.generate_order_template(1234, extras, quantity=1) + self.assertEqual(result, {'presetId': '', 'hardware': [{'domain': 'example.com', 'hostname': 'test1'}], 'useHourlyPricing': '', @@ -149,15 +154,22 @@ def test_generate_order_template(self): 'quantity': 1}) def test_generate_order_template_virtual(self): - result = self.ordering.generate_order_template( - 1234, [{'hostname': 'test1', 'domain': 'example.com'}], quantity=1) - self.assertEqual(result, {'presetId': None, + extras = { + 'hardware': [{ + 'hostname': 'test1', + 'domain': 'example.com' + }], + 'testProperty': 100 + } + result = self.ordering.generate_order_template(1234, extras, quantity=1) + self.assertEqual(result, {'presetId': '', 'hardware': [{'domain': 'example.com', 'hostname': 'test1'}], 'useHourlyPricing': '', 'packageId': 50, 'prices': [{'id': 1921}], - 'quantity': 1}) + 'quantity': 1, + 'testProperty': 100}) def test_generate_order_template_extra_quantity(self): self.assertRaises(ValueError, From 4c21f93836f216283a5580b1f69a298879e73518 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 18 Apr 2019 14:40:22 -0500 Subject: [PATCH 0067/1582] doc updates --- SoftLayer/CLI/order/quote.py | 16 +++++++++++++- SoftLayer/managers/ordering.py | 2 +- docs/api/client.rst | 40 ++++++++++++++++++++++++---------- docs/cli/ordering.rst | 14 +++++++++++- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index 7f058bf44..48c2f9766 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -79,7 +79,21 @@ def _parse_create_args(client, args): @click.option('--image', help="Image ID. See: 'slcli image list' for reference") @environment.pass_env def cli(env, quote, **args): - """View and Order a quote""" + """View and Order a quote + + :note: + The hostname and domain are split out from the fully qualified domain name. + + If you want to order multiple servers, you need to specify each FQDN. Postinstall, userdata, and + sshkeys are applied to all servers in an order. + + :: + + slcli order quote 12345 --fqdn testing.tester.com \\ + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest -k sshKeyNameLabel\\ + -i https://domain.com/runthis.sh --userdata DataGoesHere + + """ table = formatting.Table([ 'Id', 'Name', 'Created', 'Expiration', 'Status' ]) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index ccd490b0a..e8224df35 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -133,7 +133,7 @@ def get_package_id_by_type(self, package_type): raise ValueError("No package found for type: " + package_type) def get_quotes(self): - """Retrieve a list of quotes. + """Retrieve a list of active quotes. :returns: a list of SoftLayer_Billing_Order_Quote """ diff --git a/docs/api/client.rst b/docs/api/client.rst index 6c447bead..9fc13c1e7 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -86,9 +86,9 @@ offsets, and retrieving objects by id. The following section assumes you have an initialized client named 'client'. The best way to test our setup is to call the -`getObject `_ +`getObject `_ method on the -`SoftLayer_Account `_ +`SoftLayer_Account `_ service. :: @@ -97,7 +97,7 @@ service. For a more complex example we'll retrieve a support ticket with id 123456 along with the ticket's updates, the user it's assigned to, the servers attached to it, and the datacenter those servers are in. To retrieve our extra information -using an `object mask `_. +using an `object mask `_. Retrieve a ticket using object masks. :: @@ -106,22 +106,28 @@ Retrieve a ticket using object masks. id=123456, mask="updates, assignedUser, attachedHardware.datacenter") -Now add an update to the ticket with -`Ticket.addUpdate `_. +Now add an update to the ticket with `Ticket.addUpdate `_. This uses a parameter, which translate to positional arguments in the order that they appear in the API docs. + + :: update = client.call('Ticket', 'addUpdate', {'entry' : 'Hello!'}, id=123456) Let's get a listing of virtual guests using the domain example.com + + :: client.call('Account', 'getVirtualGuests', filter={'virtualGuests': {'domain': {'operation': 'example.com'}}}) -This call gets tickets created between the beginning of March 1, 2013 and -March 15, 2013. +This call gets tickets created between the beginning of March 1, 2013 and March 15, 2013. +More information on `Object Filters `_. + +:NOTE: The `value` field for startDate and endDate is in `[]`, if you do not put the date in brackets the filter will not work. + :: client.call('Account', 'getTickets', @@ -141,14 +147,24 @@ March 15, 2013. SoftLayer's XML-RPC API also allows for pagination. :: - client.call('Account', 'getVirtualGuests', limit=10, offset=0) # Page 1 - client.call('Account', 'getVirtualGuests', limit=10, offset=10) # Page 2 + from pprint import pprint + + page1 = client.call('Account', 'getVirtualGuests', limit=10, offset=0) # Page 1 + page2 = client.call('Account', 'getVirtualGuests', limit=10, offset=10) # Page 2 + + #Automatic Pagination (v5.5.3+), default limit is 100 + result = client.call('Account', 'getVirtualGuests', iter=True, limit=10) + pprint(result) + + # Using a python generator, default limit is 100 + results = client.iter_call('Account', 'getVirtualGuests', limit=10) + for result in results: + pprint(result) - #Automatic Pagination (v5.5.3+) - client.call('Account', 'getVirtualGuests', iter=True) # Page 2 +:NOTE: `client.call(iter=True)` will pull all results, then return. `client.iter_call()` will return a generator, and only make API calls as you iterate over the results. Here's how to create a new Cloud Compute Instance using -`SoftLayer_Virtual_Guest.createObject `_. +`SoftLayer_Virtual_Guest.createObject `_. Be warned, this call actually creates an hourly virtual server so this will have billing implications. :: diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 3f2eed717..acaf3a07e 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -1,7 +1,7 @@ .. _cli_order: Ordering -========== +======== The Order :ref:`cli` commands can be used to build an order for any product in the SoftLayer catalog. The basic flow for ordering goes something like this... @@ -116,10 +116,22 @@ order place --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + +Quotes +====== +.. click:: SoftLayer.CLI.order.quote:cli + :prog: order quote + :show-nested: + + .. click:: SoftLayer.CLI.order.quote_list:cli :prog: order quote-list :show-nested: +.. click:: SoftLayer.CLI.order.quote_detail:cli + :prog: order quote-detail + :show-nested: + .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: From b73a46b28345ea8b43cd28d0bc01e645006409d2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 18 Apr 2019 14:46:40 -0500 Subject: [PATCH 0068/1582] style updates --- SoftLayer/CLI/order/quote.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index 48c2f9766..498dbdc56 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -81,6 +81,7 @@ def _parse_create_args(client, args): def cli(env, quote, **args): """View and Order a quote + \f :note: The hostname and domain are split out from the fully qualified domain name. From 47c019a5bb94ffcc13c02f92a0652ef49966a1a9 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 22 Apr 2019 15:58:15 -0500 Subject: [PATCH 0069/1582] fixed verifyOrder SoftLayerAPIError(SoftLayer_Exception_Public): Reserved Capacity #0 not found --- .../fixtures/SoftLayer_Billing_Order_Quote.py | 1 + SoftLayer/managers/ordering.py | 10 ++++- tests/managers/ordering_tests.py | 39 ++++++++++++------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py index d051d6f86..f1ca8c497 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py @@ -39,6 +39,7 @@ 'quantity': 1, 'packageId': 50, 'useHourlyPricing': '', + 'reservedCapacityId': '', } diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e8224df35..e20378914 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -198,8 +198,16 @@ def verify_quote(self, quote_id, extra): :param int quantity: Quantity to override default """ container = self.generate_order_template(quote_id, extra) + clean_container = {} - return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', container, id=quote_id) + # There are a few fields that wil cause exceptions in the XML endpoing if you send in '' + # reservedCapacityId and hostId specifically. But we clean all just to be safe. + # This for some reason is only a problem on verify_quote. + for key in container.keys(): + if container.get(key) != '': + clean_container[key] = container[key] + + return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', clean_container, id=quote_id) def order_quote(self, quote_id, extra): """Places an order using a quote diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 665be1c70..e7cba8a3c 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -145,13 +145,8 @@ def test_generate_order_template(self): extras = {'hardware': [{'hostname': 'test1', 'domain': 'example.com'}]} result = self.ordering.generate_order_template(1234, extras, quantity=1) - self.assertEqual(result, {'presetId': '', - 'hardware': [{'domain': 'example.com', - 'hostname': 'test1'}], - 'useHourlyPricing': '', - 'packageId': 50, - 'prices': [{'id': 1921}], - 'quantity': 1}) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getRecalculatedOrderContainer') + self.assertEqual(result['hardware'][0]['domain'], 'example.com') def test_generate_order_template_virtual(self): extras = { @@ -162,14 +157,8 @@ def test_generate_order_template_virtual(self): 'testProperty': 100 } result = self.ordering.generate_order_template(1234, extras, quantity=1) - self.assertEqual(result, {'presetId': '', - 'hardware': [{'domain': 'example.com', - 'hostname': 'test1'}], - 'useHourlyPricing': '', - 'packageId': 50, - 'prices': [{'id': 1921}], - 'quantity': 1, - 'testProperty': 100}) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getRecalculatedOrderContainer') + self.assertEqual(result['testProperty'], 100) def test_generate_order_template_extra_quantity(self): self.assertRaises(ValueError, @@ -666,3 +655,23 @@ def test_issues1067(self): package = 'DUAL_INTEL_XEON_PROCESSOR_SCALABLE_FAMILY_4_DRIVES' result = self.ordering.get_price_id_list(package, item_keynames, None) self.assertIn(201161, result) + + + def test_clean_quote_verify(self): + from pprint import pprint as pp + extras = { + 'hardware': [{ + 'hostname': 'test1', + 'domain': 'example.com' + }], + 'testProperty': '' + } + result = self.ordering.verify_quote(1234, extras) + + self.assertEqual(result, fixtures.SoftLayer_Billing_Order_Quote.verifyOrder) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder') + call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder')[0] + order_container = call.args[0] + self.assertNotIn('testProperty', order_container) + self.assertNotIn('reservedCapacityId', order_container) + From 3cbda2a36a105fdaa12fc155f0344cd5031daaaa Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 22 Apr 2019 16:06:24 -0500 Subject: [PATCH 0070/1582] tox style fixes --- tests/managers/ordering_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index e7cba8a3c..40f9f5db3 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -656,9 +656,7 @@ def test_issues1067(self): result = self.ordering.get_price_id_list(package, item_keynames, None) self.assertIn(201161, result) - def test_clean_quote_verify(self): - from pprint import pprint as pp extras = { 'hardware': [{ 'hostname': 'test1', @@ -674,4 +672,3 @@ def test_clean_quote_verify(self): order_container = call.args[0] self.assertNotIn('testProperty', order_container) self.assertNotIn('reservedCapacityId', order_container) - From 1bd5deb16cab80f8a51c19135263b47a04eca0d4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 23 Apr 2019 13:22:26 -0400 Subject: [PATCH 0071/1582] Refactor object storage credentials. --- SoftLayer/CLI/object_storage/credential/delete.py | 5 ++--- tests/CLI/modules/object_storage_tests.py | 6 ++---- tests/managers/object_storage_tests.py | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py index 10d7dc655..7b066ba59 100644 --- a/SoftLayer/CLI/object_storage/credential/delete.py +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -9,7 +9,7 @@ @click.command() @click.argument('identifier') -@click.option('--credential_id', '-id', type=click.INT, +@click.option('--credential_id', '-c', type=click.INT, help="This is the credential id associated with the volume") @environment.pass_env def cli(env, identifier, credential_id): @@ -18,5 +18,4 @@ def cli(env, identifier, credential_id): mgr = SoftLayer.ObjectStorageManager(env.client) credential = mgr.delete_credential(identifier, credential_id=credential_id) - if credential: - env.fout("The credential was deleted successful") + env.fout(credential) diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 28856a964..8b9672da6 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -67,12 +67,10 @@ def test_delete_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') accounts.return_value = True - result = self.run_command(['object-storage', 'credential', 'delete', '-id=100', '100']) + result = self.run_command(['object-storage', 'credential', 'delete', '-c=100', '100']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - 'The credential was deleted successful' - ) + self.assertEqual(json.loads(result.output), True) def test_limit_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index c10524466..0bcca1274 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -69,7 +69,7 @@ def test_delete_credential(self): accounts.return_value = 'The credential was deleted successful' credential = self.object_storage.delete_credential(100) - self.assertEqual(credential, 'The credential was deleted successful') + self.assertEqual(credential, True) def test_limit_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') From 665ad1d1d924649f1153aa8d7df6d268b1c1b1da Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 23 Apr 2019 14:37:37 -0400 Subject: [PATCH 0072/1582] Refactor object storage credentials. --- tests/CLI/modules/object_storage_tests.py | 5 ++--- tests/managers/object_storage_tests.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 8b9672da6..74d70152e 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -67,10 +67,10 @@ def test_delete_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') accounts.return_value = True - result = self.run_command(['object-storage', 'credential', 'delete', '-c=100', '100']) + result = self.run_command(['object-storage', 'credential', 'delete', '-c', 100, '100']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), True) + self.assertEqual(result.output, 'True\n') def test_limit_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') @@ -95,7 +95,6 @@ def test_list_credential(self): result = self.run_command(['object-storage', 'credential', 'list', '100']) self.assert_no_fail(result) - print(json.loads(result.output)) self.assertEqual(json.loads(result.output), [{'id': 1103123, 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 0bcca1274..e5042080d 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -66,7 +66,7 @@ def test_create_credential(self): def test_delete_credential(self): accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') - accounts.return_value = 'The credential was deleted successful' + accounts.return_value = True credential = self.object_storage.delete_credential(100) self.assertEqual(credential, True) From 23d8188131cf0959cdf65da0e64b101c6542e01e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 23 Apr 2019 17:56:13 -0400 Subject: [PATCH 0073/1582] Feature usage vs information. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/usage.py | 53 +++++++++++++++++++ .../SoftLayer_Metric_Tracking_Object.py | 12 +++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 2 + SoftLayer/managers/vs.py | 21 ++++++++ tests/CLI/modules/vs/vs_tests.py | 27 ++++++++++ tests/managers/vs/vs_tests.py | 28 ++++++++++ 7 files changed, 144 insertions(+) create mode 100644 SoftLayer/CLI/virt/usage.py create mode 100644 SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 81ba46672..6736b233b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -36,6 +36,7 @@ ('virtual:reboot', 'SoftLayer.CLI.virt.power:reboot'), ('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'), ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), + ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py new file mode 100644 index 000000000..fdee54d2a --- /dev/null +++ b/SoftLayer/CLI/virt/usage.py @@ -0,0 +1,53 @@ +"""Usage information of a virtual server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.utils import clean_time + + +@click.command() +@click.argument('identifier') +@click.option('--start_date', '-s', type=click.STRING, required=True, help="Start Date e.g. 2019-3-4 (yyyy-MM-dd)") +@click.option('--end_date', '-e', type=click.STRING, required=True, help="End Date e.g. 2019-4-2 (yyyy-MM-dd)") +@click.option('--valid_type', '-t', type=click.STRING, required=True, + help="Metric_Data_Type keyName e.g. CPU0, CPU1, MEMORY_USAGE, etc.") +@click.option('--summary_period', '-p', type=click.INT, default=1800, + help="300, 600, 1800, 3600, 43200 or 86400 seconds") +@environment.pass_env +def cli(env, identifier, start_date, end_date, valid_type, summary_period): + """Usage information of a virtual server.""" + + vsi = SoftLayer.VSManager(env.client) + table = formatting.Table(['counter', 'dateTime', 'type']) + table_average = formatting.Table(['Average']) + + 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) + + count = 0 + counter = 0.0 + for data in result: + table.add_row([ + data['counter'], + clean_time(data['dateTime']), + data['type'], + ]) + counter = counter + float(data['counter']) + count = count + 1 + + if type == "MEMORY_USAGE": + average = (counter / count) / 2 ** 30 + else: + average = counter / count + + env.fout(table_average.add_row([average])) + + env.fout(table_average) + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py new file mode 100644 index 000000000..6a0a031a2 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py @@ -0,0 +1,12 @@ +getSummaryData = [ + { + "counter": 1.44, + "dateTime": "2019-03-04T00:00:00-06:00", + "type": "cpu0" + }, + { + "counter": 1.53, + "dateTime": "2019-03-04T00:05:00-06:00", + "type": "cpu0" + }, +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c01fd17a8..49433b01f 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -626,3 +626,5 @@ } }, ] + +getMetricTrackingObjectId = 1000 diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 00b738d0c..073b8aeab 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1002,6 +1002,27 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public else: return price.get('id') + def get_summary_data_usage(self, instance_id, start_date=None, end_date=None, valid_type=None, summary_period=None): + """Retrieve the usage information of a virtual server. + + :param string instance_id: a string identifier used to resolve ids + :param string start_date: the start data to retrieve the vs usage information + :param string end_date: the start data to retrieve the vs usage information + :param string string valid_type: the Metric_Data_Type keyName. + :param int summary_period: summary period. + """ + valid_types = [ + { + "keyName": valid_type, + "summaryType": "max" + } + ] + + metric_tracking_id = self.guest.getMetricTrackingObjectId(id=instance_id) + + return self.client.call('Metric_Tracking_Object', 'getSummaryData', start_date, end_date, valid_types, + summary_period, id=metric_tracking_id, iter=True) + # pylint: disable=inconsistent-return-statements def _get_price_id_for_upgrade(self, package_items, option, value, public=True): """Find the price id for the option and value to upgrade. diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index ce1bb9d73..9c5d155fc 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -660,3 +660,30 @@ def test_vs_capture(self): result = self.run_command(['vs', 'capture', '100', '--name', 'TestName']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Virtual_Guest', 'createArchiveTransaction', identifier=100) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_usage_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['vs', 'usage', '100']) + self.assertEqual(result.exit_code, 2) + + def test_usage_vs(self): + result = self.run_command( + ['vs', 'usage', '100']) + self.assertEqual(result.exit_code, 2) + + def test_usage_vs_cpu(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', + '--summary_period=300']) + + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index f13c3e7b9..47674345f 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -830,3 +830,31 @@ def test_capture_additional_disks(self): 'createArchiveTransaction', args=args, identifier=1) + + def test_usage_vs_cpu(self): + result = self.vs.get_summary_data_usage('100', + start_date='2019-3-4', + end_date='2019-4-2', + valid_type='CPU0', + summary_period=300) + + expected = fixtures.SoftLayer_Metric_Tracking_Object.getSummaryData + self.assertEqual(result, expected) + + args = ('2019-3-4', '2019-4-2', [{"keyName": "CPU0", "summaryType": "max"}], 300) + + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', args=args, identifier=1000) + + def test_usage_vs_memory(self): + result = self.vs.get_summary_data_usage('100', + start_date='2019-3-4', + end_date='2019-4-2', + valid_type='MEMORY_USAGE', + summary_period=300) + + expected = fixtures.SoftLayer_Metric_Tracking_Object.getSummaryData + self.assertEqual(result, expected) + + args = ('2019-3-4', '2019-4-2', [{"keyName": "MEMORY_USAGE", "summaryType": "max"}], 300) + + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', args=args, identifier=1000) From a911fac51589216af15f667c256cce303681b349 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 23 Apr 2019 18:01:11 -0400 Subject: [PATCH 0074/1582] Refactor usage vs information. --- SoftLayer/CLI/virt/usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py index fdee54d2a..68981705c 100644 --- a/SoftLayer/CLI/virt/usage.py +++ b/SoftLayer/CLI/virt/usage.py @@ -42,7 +42,7 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): counter = counter + float(data['counter']) count = count + 1 - if type == "MEMORY_USAGE": + if valid_type == "MEMORY_USAGE": average = (counter / count) / 2 ** 30 else: average = counter / count From 7aa6eb2c5899e3c317f7932724c53f06396b2b3e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 24 Apr 2019 14:28:39 -0500 Subject: [PATCH 0075/1582] #1131 made sure config_tests dont actually try to make api calls --- tests/CLI/modules/config_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 4fe9cf867..ec018a53c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -75,10 +75,12 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): self.assertTrue('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in contents) self.assertTrue('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT in contents) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_setup_cancel(self, mocked_input, getpass, confirm_mock): + def test_setup_cancel(self, mocked_input, getpass, confirm_mock, client): + client.return_value = self.env.client with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = False getpass.return_value = 'A' * 64 From d27a94e51ccae312ad11b0beac86473f15e6701b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 29 Apr 2019 15:15:26 -0400 Subject: [PATCH 0076/1582] Refactor usage vs information. --- SoftLayer/CLI/virt/usage.py | 22 ++++++++++++++-------- tests/CLI/modules/vs/vs_tests.py | 11 ++++++++++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py index 68981705c..ec9702216 100644 --- a/SoftLayer/CLI/virt/usage.py +++ b/SoftLayer/CLI/virt/usage.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers from SoftLayer.utils import clean_time @@ -31,23 +32,28 @@ def cli(env, identifier, start_date, end_date, valid_type, summary_period): result = vsi.get_summary_data_usage(vs_id, start_date=start_date, end_date=end_date, valid_type=valid_type, summary_period=summary_period) + if len(result) == 0: + raise exceptions.CLIAbort('No metric data for this range of dates provided') + count = 0 - counter = 0.0 + counter = 0.00 for data in result: + if valid_type == "MEMORY_USAGE": + usage_counter = data['counter'] / 2 ** 30 + else: + usage_counter = data['counter'] + table.add_row([ - data['counter'], + round(usage_counter, 2), clean_time(data['dateTime']), data['type'], ]) - counter = counter + float(data['counter']) + counter = counter + usage_counter count = count + 1 - if valid_type == "MEMORY_USAGE": - average = (counter / count) / 2 ** 30 - else: - average = counter / count + average = counter / count - env.fout(table_average.add_row([average])) + env.fout(table_average.add_row([round(average, 2)])) env.fout(table_average) env.fout(table) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 9c5d155fc..7b03bb084 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -681,9 +681,18 @@ def test_usage_vs_cpu(self): 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', '--summary_period=300']) self.assert_no_fail(result) + + def test_usage_metric_data_empty(self): + usage_vs = self.set_mock('SoftLayer_Metric_Tracking_Object', 'getSummaryData') + test_usage = [] + usage_vs.return_value = test_usage + result = self.run_command( + ['vs', 'usage', '100', '--start_date=2019-3-4', '--end_date=2019-4-2', '--valid_type=CPU0', + '--summary_period=300']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) From 4495d0d074c5b55394e9e562d40775228698c3c3 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 3 May 2019 14:45:05 -0500 Subject: [PATCH 0077/1582] 5.7.2 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f10106386..9cf8c1149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Change Log + +## [5.7.2] - 2019-05-03 +- https://github.com/softlayer/softlayer-python/compare/v5.7.1...v5.7.2 + ++ #1107 Added exception to handle json parsing error when ordering ++ #1068 Support for -1 when changing port speed ++ #1109 Fixed docs about placement groups ++ #1112 File storage endurance iops upgrade ++ #1101 Handle the new user creation exceptions ++ #1116 Fix order place quantity option ++ #1002 Invoice commands + * account invoices + * account invoice-detail + * account summary ++ #1004 Event Notification Management commands + * account events + * account event-detail ++ #1117 Two PCIe items can be added at order time ++ #1121 Fix object storage apiType for S3 and Swift. ++ #1100 Event Log performance improvements. ++ #872 column 'name' was renamed to 'hostname' ++ #1127 Fix object storage credentials. ++ #1129 Fixed unexpected errors in slcli subnet create ++ #1134 Change encrypt parameters for importing of images. Adds root-key-crn ++ #208 Quote ordering commands + * order quote + * order quote-detail + * order quote-list ++ #1113 VS usage information command + * virtual usage ++ #1131 made sure config_tests dont actually make api calls. + + ## [5.7.1] - 2019-02-26 - https://github.com/softlayer/softlayer-python/compare/v5.7.0...v5.7.1 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index f3120e27e..a9927d986 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.7.1' +VERSION = 'v5.7.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 12835570f..abe08c52e 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.7.1', + version='5.7.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c464f9693..d6634a551 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.7.1+git' # check versioning +version: '5.7.2+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From fa7c1fe86fe74f516cde53a68e8d0af05839d301 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 3 May 2019 15:49:14 -0500 Subject: [PATCH 0078/1582] updating release process --- RELEASE.md | 8 +++++++- fabfile.py | 58 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 75eea45dc..eb1cb6d47 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,7 +3,13 @@ * Update version constants (find them by running `git grep [VERSION_NUMBER]`) * Create changelog entry (edit CHANGELOG.md with a one-liner for each closed issue going in the release) * Commit and push changes to master with the message: "Version Bump to v[VERSION_NUMBER]" -* Push tag and PyPi `fab release:[VERSION_NUMBER]`. Before you do this, make sure you have the organization repository set up as upstream remote & fabric installed (`pip install fabric`), also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: +* Make sure your `upstream` repo is set +``` +git remote -v +upstream git@github.com:softlayer/softlayer-python.git (fetch) +upstream git@github.com:softlayer/softlayer-python.git (push) +``` +* Push tag and PyPi `python fabfile.py 5.7.2`. Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: ``` [server-login] diff --git a/fabfile.py b/fabfile.py index cd6a968f5..a393fe99b 100644 --- a/fabfile.py +++ b/fabfile.py @@ -1,49 +1,67 @@ +import click import os.path import shutil - -from fabric.api import local, lcd, puts, abort - +import subprocess +import sys +from pprint import pprint as pp def make_html(): - "Build HTML docs" - with lcd('docs'): - local('make html') - + """Build HTML docs""" + click.secho("Building HTML") + subprocess.run('make html', cwd='docs', shell=True) def upload(): - "Upload distribution to PyPi" - local('python setup.py sdist bdist_wheel') - local('twine upload dist/*') + """Upload distribution to PyPi""" + cmd_setup = 'python setup.py sdist bdist_wheel' + click.secho("\tRunning %s" % cmd_setup, fg='yellow') + subprocess.run(cmd_setup, shell=True) + cmd_twine = 'twine upload dist/*' + click.secho("\tRunning %s" % cmd_twine, fg='yellow') + subprocess.run(cmd_twine, shell=True) def clean(): - puts("* Cleaning Repo") + click.secho("* Cleaning Repo") directories = ['.tox', 'SoftLayer.egg-info', 'build', 'dist'] for directory in directories: if os.path.exists(directory) and os.path.isdir(directory): shutil.rmtree(directory) -def release(version, force=False): +@click.command() +@click.argument('version') +@click.option('--force', default=False, is_flag=True, help="Force upload") +def release(version, force): """Perform a release. Example: - $ fab release:3.0.0 + $ python fabfile.py 1.2.3 """ if version.startswith("v"): - abort("Version should not start with 'v'") + exit("Version should not start with 'v'") version_str = "v%s" % version clean() - local("pip install wheel") + subprocess.run("pip install wheel", shell=True) - puts(" * Uploading to PyPI") + print(" * Uploading to PyPI") upload() + make_html() - puts(" * Tagging Version %s" % version_str) force_option = 'f' if force else '' - local("git tag -%sam \"%s\" %s" % (force_option, version_str, version_str)) + cmd_tag = "git tag -%sam \"%s\" %s" % (force_option, version_str, version_str) + + click.secho(" * Tagging Version %s" % version_str) + click.secho("\tRunning %s" % cmd_tag, fg='yellow') + subprocess.run(cmd_tag, shell=True) + + + cmd_push = "git push upstream %s" % version_str + click.secho(" * Pushing Tag to upstream") + click.secho("\tRunning %s" % cmd_push, fg='yellow') + subprocess.run(cmd_push, shell=True) + - puts(" * Pushing Tag to upstream") - local("git push upstream %s" % version_str) +if __name__ == '__main__': + release() \ No newline at end of file From 9a84266584cd2435a316ef69943013a7aebbd17e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 3 May 2019 16:13:39 -0500 Subject: [PATCH 0079/1582] updates for doc generation --- .readthedocs.yml | 23 +++++++++++++++++++++++ docs/requirements.txt | 4 ++++ 2 files changed, 27 insertions(+) create mode 100644 .readthedocs.yml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..a36db8a13 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,23 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..acb2b7258 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +sphinx +sphinx-click +click +prettytable \ No newline at end of file From e3046b973b1940caf15384de07b90ec01e630ea4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 3 May 2019 16:21:49 -0500 Subject: [PATCH 0080/1582] getting readthedocs builds to work --- .readthedocs.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..73e4a4e5d --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,24 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + builder: htmldir + configuration: docs/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt From ab01bb2b7c9f0d16c80ba1deee6a7311d46c242e Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 8 May 2019 15:42:07 -0500 Subject: [PATCH 0081/1582] Upgrade to prompt_toolkit >= 2 --- README.rst | 10 +++++----- SoftLayer/shell/completer.py | 5 ++--- SoftLayer/shell/core.py | 10 ++++++---- setup.py | 2 +- tools/requirements.txt | 2 +- tools/test-requirements.txt | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/README.rst b/README.rst index 177f15143..66acc1d1d 100644 --- a/README.rst +++ b/README.rst @@ -132,12 +132,12 @@ System Requirements Python Packages --------------- * six >= 1.7.0 -* prettytable >= 0.7.0 -* click >= 5, < 7 -* requests >= 2.18.4 -* prompt_toolkit >= 0.53 +* ptable >= 0.9.2 +* click >= 7 +* requests >= 2.20.0 +* prompt_toolkit >= 2 * pygments >= 2.0.0 -* urllib3 >= 1.22 +* urllib3 >= 1.24 Copyright --------- diff --git a/SoftLayer/shell/completer.py b/SoftLayer/shell/completer.py index 1f59f3a53..fb94fd50e 100644 --- a/SoftLayer/shell/completer.py +++ b/SoftLayer/shell/completer.py @@ -24,18 +24,17 @@ def get_completions(self, document, complete_event): return _click_autocomplete(self.root, document.text_before_cursor) -# pylint: disable=stop-iteration-return def _click_autocomplete(root, text): """Completer generator for click applications.""" try: parts = shlex.split(text) except ValueError: - raise StopIteration + return location, incomplete = _click_resolve_command(root, parts) if not text.endswith(' ') and not incomplete and text: - raise StopIteration + return if incomplete and not incomplete[0:2].isalnum(): for param in location.params: diff --git a/SoftLayer/shell/core.py b/SoftLayer/shell/core.py index 32c250584..55a56e888 100644 --- a/SoftLayer/shell/core.py +++ b/SoftLayer/shell/core.py @@ -13,8 +13,8 @@ import traceback import click -from prompt_toolkit import auto_suggest as p_auto_suggest -from prompt_toolkit import shortcuts as p_shortcuts +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit import PromptSession from SoftLayer.CLI import core from SoftLayer.CLI import environment @@ -48,12 +48,14 @@ def cli(ctx, env): os.makedirs(app_path) complete = completer.ShellCompleter(core.cli) + session = PromptSession() + while True: try: - line = p_shortcuts.prompt( + line = session.prompt( completer=complete, complete_while_typing=True, - auto_suggest=p_auto_suggest.AutoSuggestFromHistory(), + auto_suggest=AutoSuggestFromHistory(), ) # Parse arguments diff --git a/setup.py b/setup.py index abe08c52e..692ef789d 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ 'ptable >= 0.9.2', 'click >= 7', 'requests >= 2.20.0', - 'prompt_toolkit >= 0.53', + 'prompt_toolkit >= 2', 'pygments >= 2.0.0', 'urllib3 >= 1.24' ], diff --git a/tools/requirements.txt b/tools/requirements.txt index cd4a89429..0d7746444 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -2,6 +2,6 @@ six >= 1.7.0 ptable >= 0.9.2 click >= 7 requests >= 2.20.0 -prompt_toolkit >= 0.53 +prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 \ No newline at end of file diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 56ba8fe65..2869de5e6 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -8,6 +8,6 @@ six >= 1.7.0 ptable >= 0.9.2 click >= 7 requests >= 2.20.0 -prompt_toolkit >= 0.53 +prompt_toolkit >= 2 pygments >= 2.0.0 urllib3 >= 1.24 \ No newline at end of file From 105b9eef07968f46cd9d2f77e91d953b8e509a18 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 14 May 2019 12:02:53 -0500 Subject: [PATCH 0082/1582] Fix shell CLI tests. --- SoftLayer/testing/__init__.py | 4 ++-- tests/CLI/modules/shell_tests.py | 32 +++++++++++++++++--------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index d5279c03f..d7c816918 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -157,7 +157,7 @@ def set_mock(self, service, method): """Set and return mock on the current client.""" return self.mocks.set_mock(service, method) - def run_command(self, args=None, env=None, fixtures=True, fmt='json'): + def run_command(self, args=None, env=None, fixtures=True, fmt='json', input=None): """A helper that runs a SoftLayer CLI command. This returns a click.testing.Result object. @@ -169,7 +169,7 @@ def run_command(self, args=None, env=None, fixtures=True, fmt='json'): args.insert(0, '--format=%s' % fmt) runner = testing.CliRunner() - return runner.invoke(core.cli, args=args, obj=env or self.env) + return runner.invoke(core.cli, args=args, input=input, obj=env or self.env) def call_has_props(call, props): diff --git a/tests/CLI/modules/shell_tests.py b/tests/CLI/modules/shell_tests.py index bf71d7004..9e094b8db 100644 --- a/tests/CLI/modules/shell_tests.py +++ b/tests/CLI/modules/shell_tests.py @@ -6,22 +6,24 @@ """ from SoftLayer import testing -import mock +import tempfile class ShellTests(testing.TestCase): - @mock.patch('prompt_toolkit.shortcuts.prompt') - def test_shell_quit(self, prompt): - prompt.return_value = "quit" - result = self.run_command(['shell']) - self.assertEqual(result.exit_code, 0) - @mock.patch('prompt_toolkit.shortcuts.prompt') - @mock.patch('shlex.split') - def test_shell_help(self, prompt, split): - split.side_effect = [(['help']), (['vs', 'list']), (False), (['quit'])] - prompt.return_value = "none" - result = self.run_command(['shell']) - if split.call_count is not 5: - raise Exception("Split not called correctly. Count: " + str(split.call_count)) - self.assertEqual(result.exit_code, 1) + def test_shell_quit(self): + # Use a file as stdin + with tempfile.NamedTemporaryFile() as stdin: + stdin.write(b'exit\n') + stdin.seek(0) + result = self.run_command(['shell'], input=stdin) + self.assertEqual(result.exit_code, 0) + + def test_shell_help(self): + # Use a file as stdin + with tempfile.NamedTemporaryFile() as stdin: + stdin.write(b'help\nexit\n') + stdin.seek(0) + result = self.run_command(['shell'], input=stdin) + self.assertIn('Welcome to the SoftLayer shell.', result.output) + self.assertEqual(result.exit_code, 0) From 857f33eddf2aad583962fa2a710dd879fc7afabc Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 14 May 2019 14:32:25 -0500 Subject: [PATCH 0083/1582] Don't shadow input with the new parameter --- SoftLayer/testing/__init__.py | 4 ++-- tests/CLI/modules/shell_tests.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index d7c816918..87d7f5e41 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -157,7 +157,7 @@ def set_mock(self, service, method): """Set and return mock on the current client.""" return self.mocks.set_mock(service, method) - def run_command(self, args=None, env=None, fixtures=True, fmt='json', input=None): + def run_command(self, args=None, env=None, fixtures=True, fmt='json', stdin=None): """A helper that runs a SoftLayer CLI command. This returns a click.testing.Result object. @@ -169,7 +169,7 @@ def run_command(self, args=None, env=None, fixtures=True, fmt='json', input=None args.insert(0, '--format=%s' % fmt) runner = testing.CliRunner() - return runner.invoke(core.cli, args=args, input=input, obj=env or self.env) + return runner.invoke(core.cli, args=args, input=stdin, obj=env or self.env) def call_has_props(call, props): diff --git a/tests/CLI/modules/shell_tests.py b/tests/CLI/modules/shell_tests.py index 9e094b8db..d082a38db 100644 --- a/tests/CLI/modules/shell_tests.py +++ b/tests/CLI/modules/shell_tests.py @@ -16,7 +16,7 @@ def test_shell_quit(self): with tempfile.NamedTemporaryFile() as stdin: stdin.write(b'exit\n') stdin.seek(0) - result = self.run_command(['shell'], input=stdin) + result = self.run_command(['shell'], stdin=stdin) self.assertEqual(result.exit_code, 0) def test_shell_help(self): @@ -24,6 +24,6 @@ def test_shell_help(self): with tempfile.NamedTemporaryFile() as stdin: stdin.write(b'help\nexit\n') stdin.seek(0) - result = self.run_command(['shell'], input=stdin) + result = self.run_command(['shell'], stdin=stdin) self.assertIn('Welcome to the SoftLayer shell.', result.output) self.assertEqual(result.exit_code, 0) From 4d84b4f2612a3951326ff5cd7b1290843b8e0c57 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 May 2019 15:34:44 -0500 Subject: [PATCH 0084/1582] #1003 adding bandwidth commands --- SoftLayer/CLI/hardware/bandwidth.py | 26 ++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/usage.py | 2 +- SoftLayer/managers/hardware.py | 16 ++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/hardware/bandwidth.py diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py new file mode 100644 index 000000000..46cf2de40 --- /dev/null +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -0,0 +1,26 @@ +"""Get details 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 +from SoftLayer.CLI import helpers +from SoftLayer import utils + +from pprint import pprint as pp + +@click.command() +@click.argument('identifier') +@click.option('--start_date', '-s', type=click.STRING, required=True, + help="Start Date e.g. 2019-03-04 (yyyy-MM-dd)") +@click.option('--end_date', '-e', type=click.STRING, required=True, + help="End Date e.g. 2019-04-02 (yyyy-MM-dd)") +@click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, + help="300, 600, 1800, 3600, 43200 or 86400 seconds") +@environment.pass_env +def cli(env, identifier, start_date, end_date, summary_period): + hardware = SoftLayer.HardwareManager(env.client) + data = hardware.get_bandwidth_data(identifier, start_date, end_date, summary_period) + pp(data) \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6736b233b..badd6c158 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -213,6 +213,7 @@ ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), ('hardware', 'SoftLayer.CLI.hardware'), + ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), ('hardware:cancel', 'SoftLayer.CLI.hardware.cancel:cli'), ('hardware:cancel-reasons', 'SoftLayer.CLI.hardware.cancel_reasons:cli'), ('hardware:create', 'SoftLayer.CLI.hardware.create:cli'), diff --git a/SoftLayer/CLI/virt/usage.py b/SoftLayer/CLI/virt/usage.py index ec9702216..9936d9416 100644 --- a/SoftLayer/CLI/virt/usage.py +++ b/SoftLayer/CLI/virt/usage.py @@ -17,7 +17,7 @@ @click.option('--end_date', '-e', type=click.STRING, required=True, help="End Date e.g. 2019-4-2 (yyyy-MM-dd)") @click.option('--valid_type', '-t', type=click.STRING, required=True, help="Metric_Data_Type keyName e.g. CPU0, CPU1, MEMORY_USAGE, etc.") -@click.option('--summary_period', '-p', type=click.INT, default=1800, +@click.option('--summary_period', '-p', type=click.INT, default=3600, help="300, 600, 1800, 3600, 43200 or 86400 seconds") @environment.pass_env def cli(env, identifier, start_date, end_date, valid_type, summary_period): diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 77c4e604c..f6e729b61 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -664,6 +664,22 @@ def wait_for_ready(self, instance_id, limit=14400, delay=10, pending=False): LOGGER.info("Waiting for %d expired.", instance_id) return False + def get_tracking_id(self, instance_id): + """Returns the Metric Tracking Object Id for a hardware server + + :param int instance_id: Id of the hardware server + """ + return self.hardware.getMetricTrackingObjectId(id=instance_id) + + def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direction='both', rollup=3600): + """Gets bandwidth data for a server + + """ + tracking_id = self.get_tracking_id(instance_id) + data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, rollup, + id=tracking_id) + return data + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" From 6ae31ec2dfa945907bb8729894e413c71c81dfa4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 May 2019 15:50:27 -0500 Subject: [PATCH 0085/1582] docs for hw manager --- SoftLayer/managers/hardware.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index f6e729b61..ddc2704ba 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -671,9 +671,19 @@ def get_tracking_id(self, instance_id): """ return self.hardware.getMetricTrackingObjectId(id=instance_id) - def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direction='both', rollup=3600): + def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direction=None, rollup=3600): """Gets bandwidth data for a server + Will get averaged bandwidth data for a given time period. If you use a rollup over 3600 be aware + that the API will bump your start/end date to align with how data is stored. For example if you + have a rollup of 86400 your start_date will be bumped to 00:00. If you are not using a time in the + start/end date fields, this won't really matter. + + :param int instance_id: Hardware Id to get data for + :param date start_date: Date to start pulling data for. + :param date end_date: Date to finish pulling data for + :param string direction: Can be either 'public', 'private', or None for both. + :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. """ tracking_id = self.get_tracking_id(instance_id) data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, rollup, From aa20fe9eac6c8d724f211e405ccba1b0f0a24c4a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 15 May 2019 17:02:59 -0500 Subject: [PATCH 0086/1582] adding bw useage to hw detail --- SoftLayer/CLI/hardware/bandwidth.py | 39 +++++++++++++++++++++++++---- SoftLayer/CLI/hardware/detail.py | 14 +++++++++++ SoftLayer/managers/hardware.py | 8 ++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 46cf2de40..a73a29e31 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -9,18 +9,47 @@ from SoftLayer.CLI import helpers from SoftLayer import utils -from pprint import pprint as pp @click.command() @click.argument('identifier') @click.option('--start_date', '-s', type=click.STRING, required=True, - help="Start Date e.g. 2019-03-04 (yyyy-MM-dd)") + help="Start Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss,") @click.option('--end_date', '-e', type=click.STRING, required=True, - help="End Date e.g. 2019-04-02 (yyyy-MM-dd)") + help="End Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss") @click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, help="300, 600, 1800, 3600, 43200 or 86400 seconds") @environment.pass_env def cli(env, identifier, start_date, end_date, summary_period): + """Bandwidth data over date range. Bandwidth is listed in GB + + Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data + Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. + + Example:: + + slcli hw bandwidth 1234 -s 2019-05-01T00:01 -e 2019-05-02T00:00:01.00000-12:00 + """ hardware = SoftLayer.HardwareManager(env.client) - data = hardware.get_bandwidth_data(identifier, start_date, end_date, summary_period) - pp(data) \ No newline at end of file + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + data = hardware.get_bandwidth_data(hardware_id, start_date, end_date, None, summary_period) + + formatted_data = {} + for point in data: + key = utils.clean_time(point['dateTime']) + data_type = point['type'] + value = round(point['counter'] / 2 ** 30,4) + if formatted_data.get(key) is None: + formatted_data[key] = {} + formatted_data[key][data_type] = value + + table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], + title="Bandwidth Report: %s - %s" % (start_date, end_date)) + for point in formatted_data: + table.add_row([ + point, + formatted_data[point].get('publicIn_net_octet', '-'), + formatted_data[point].get('publicOut_net_octet', '-'), + formatted_data[point].get('privateIn_net_octet', '-'), + formatted_data[point].get('privateOut_net_octet', '-') + ]) + env.fout(table) \ No newline at end of file diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 4382fc362..ed53470ed 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -28,6 +28,8 @@ def cli(env, identifier, passwords, price): result = hardware.get_hardware(hardware_id) result = utils.NestedDict(result) + bandwidth = hardware.get_bandwidth_allocation(hardware_id) + operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} memory = formatting.gb(result.get('memoryCapacity', 0)) owner = None @@ -57,6 +59,18 @@ def cli(env, identifier, passwords, price): table.add_row(['vlans', vlan_table]) + bw_table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) + for bw in bandwidth.get('useage'): + bw_type = 'Private' + allotment = 'N/A' + if bw['type']['alias'] == 'PUBLIC_SERVER_BW': + bw_type = 'Public' + allotment = bandwidth['allotment'].get('amount', '-') + + + bw_table.add_row([bw_type, bw['amountIn'], bw['amountOut'], allotment]) + table.add_row(['Bandwidth', bw_table]) + if result.get('notes'): table.add_row(['notes', result['notes']]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ddc2704ba..b66e3a875 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -690,6 +690,14 @@ def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direct id=tracking_id) return data + def get_bandwidth_allocation(self, instance_id): + """Combines getBandwidthAllotmentDetail() and getBillingCycleBandwidthUsage() """ + a_mask="mask[allocation[amount]]" + allotment = self.client.call('Hardware_Server', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) + u_mask="mask[amountIn,amountOut,type]" + useage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) + return {'allotment': allotment['allocation'], 'useage': useage} + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" From e31c1cc07e7aaba9856e9cdd2bfc152dc687a645 Mon Sep 17 00:00:00 2001 From: jbskytap <41307692+jbskytap@users.noreply.github.com> Date: Thu, 16 May 2019 09:37:22 -0700 Subject: [PATCH 0087/1582] DOCS: fix broken link --- docs/api/client.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/client.rst b/docs/api/client.rst index 9fc13c1e7..c798ac71d 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -8,7 +8,7 @@ and executing XML-RPC calls against the SoftLayer API. Below are some links that will help to use the SoftLayer API. -* `SoftLayer API Documentation `_ +* `SoftLayer API Documentation `_ * `Source on GitHub `_ :: From cd02aa737a8582fd2b151bec84b3b208cd34271b Mon Sep 17 00:00:00 2001 From: jbskytap <41307692+jbskytap@users.noreply.github.com> Date: Thu, 16 May 2019 09:44:03 -0700 Subject: [PATCH 0088/1582] DOCS: replace 'developer' with 'sldn' links --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 177f15143..24b3625e2 100644 --- a/README.rst +++ b/README.rst @@ -31,9 +31,9 @@ http://softlayer.github.io/softlayer-python/. Additional API documentation can be found on the SoftLayer Development Network: * `SoftLayer API reference - `_ + `_ * `Object mask information and examples - `_ + `_ * `Code Examples `_ From 955e91ad6302182ad8f382513d52f916b0c6e2e4 Mon Sep 17 00:00:00 2001 From: jbskytap <41307692+jbskytap@users.noreply.github.com> Date: Thu, 16 May 2019 09:45:55 -0700 Subject: [PATCH 0089/1582] replace developer links with sldn links --- SoftLayer/managers/ssl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/ssl.py b/SoftLayer/managers/ssl.py index f0d75dc37..3fa2ac1dd 100644 --- a/SoftLayer/managers/ssl.py +++ b/SoftLayer/managers/ssl.py @@ -61,7 +61,7 @@ def add_certificate(self, certificate): :param dict certificate: A dictionary representing the parts of the certificate. - See developer.softlayer.com for more info. + See sldn.softlayer.com for more info. Example:: From 048dd211357371aee1e3c24f5c1adb23e7324d80 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 16 May 2019 14:49:38 -0500 Subject: [PATCH 0090/1582] vs bandwidth commands --- SoftLayer/CLI/hardware/bandwidth.py | 49 ++++++++++++--- SoftLayer/CLI/hardware/detail.py | 4 +- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/bandwidth.py | 92 +++++++++++++++++++++++++++++ SoftLayer/CLI/virt/detail.py | 13 ++++ SoftLayer/managers/hardware.py | 4 +- SoftLayer/managers/vs.py | 36 ++++++++++- 7 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 SoftLayer/CLI/virt/bandwidth.py diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index a73a29e31..256446abb 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -18,8 +18,10 @@ help="End Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss") @click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, help="300, 600, 1800, 3600, 43200 or 86400 seconds") +@click.option('--quite_summary', '-q', is_flag=True, default=False, show_default=True, + help="Only show the summary table") @environment.pass_env -def cli(env, identifier, start_date, end_date, summary_period): +def cli(env, identifier, start_date, end_date, summary_period, quite_summary): """Bandwidth data over date range. Bandwidth is listed in GB Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data @@ -44,12 +46,43 @@ def cli(env, identifier, start_date, end_date, summary_period): table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], title="Bandwidth Report: %s - %s" % (start_date, end_date)) + + sum_table = formatting.Table(['Type','Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") + + bw_totals = [ + {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, + {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, + {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, + {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, + ] for point in formatted_data: - table.add_row([ - point, - formatted_data[point].get('publicIn_net_octet', '-'), - formatted_data[point].get('publicOut_net_octet', '-'), - formatted_data[point].get('privateIn_net_octet', '-'), - formatted_data[point].get('privateOut_net_octet', '-') + new_row = [point] + for bw_type in bw_totals: + counter = formatted_data[point].get(bw_type['keyName'], 0) + new_row.append(mb_to_gb(counter)) + bw_type['sum'] = bw_type['sum'] + counter + if counter > bw_type['max']: + bw_type['max'] = counter + bw_type['maxDate'] = point + table.add_row(new_row) + + for bw_type in bw_totals: + total = bw_type.get('sum', 0) + average = 0 + if total > 0: + average = round(total / len(formatted_data) / summary_period,4) + sum_table.add_row([ + bw_type.get('name'), + mb_to_gb(total), + average, + mb_to_gb(bw_type.get('max')), + bw_type.get('maxDate') ]) - env.fout(table) \ No newline at end of file + + env.fout(sum_table) + if not quite_summary: + env.fout(table) + + +def mb_to_gb(x): + return round(x / 2 ** 10, 4) \ No newline at end of file diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index ed53470ed..d52b7b4d0 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -28,8 +28,6 @@ def cli(env, identifier, passwords, price): result = hardware.get_hardware(hardware_id) result = utils.NestedDict(result) - bandwidth = hardware.get_bandwidth_allocation(hardware_id) - operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} memory = formatting.gb(result.get('memoryCapacity', 0)) owner = None @@ -59,6 +57,7 @@ def cli(env, identifier, passwords, price): table.add_row(['vlans', vlan_table]) + bandwidth = hardware.get_bandwidth_allocation(hardware_id) bw_table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) for bw in bandwidth.get('useage'): bw_type = 'Private' @@ -67,7 +66,6 @@ def cli(env, identifier, passwords, price): bw_type = 'Public' allotment = bandwidth['allotment'].get('amount', '-') - bw_table.add_row([bw_type, bw['amountIn'], bw['amountOut'], allotment]) table.add_row(['Bandwidth', bw_table]) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index badd6c158..62c1baa47 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -19,6 +19,7 @@ ('account:summary', 'SoftLayer.CLI.account.summary:cli'), ('virtual', 'SoftLayer.CLI.virt'), + ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), ('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'), ('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'), ('virtual:create', 'SoftLayer.CLI.virt.create:cli'), diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py new file mode 100644 index 000000000..cc657c7f1 --- /dev/null +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -0,0 +1,92 @@ +"""Get details 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 +from SoftLayer.CLI import helpers +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--start_date', '-s', type=click.STRING, required=True, + help="Start Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss,") +@click.option('--end_date', '-e', type=click.STRING, required=True, + help="End Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss") +@click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, + help="300, 600, 1800, 3600, 43200 or 86400 seconds") +@click.option('--quite_summary', '-q', is_flag=True, default=False, show_default=True, + help="Only show the summary table") +@environment.pass_env +def cli(env, identifier, start_date, end_date, summary_period, quite_summary): + """Bandwidth data over date range. Bandwidth is listed in GB + + Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data + Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. + + Example:: + + slcli hw bandwidth 1234 -s 2019-05-01T00:01 -e 2019-05-02T00:00:01.00000-12:00 + """ + 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) + + formatted_data = {} + for point in data: + key = utils.clean_time(point['dateTime']) + data_type = point['type'] + # conversion from byte to megabyte + value = round(point['counter'] / 2 ** 20,4) + if formatted_data.get(key) is None: + formatted_data[key] = {} + formatted_data[key][data_type] = value + + table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], + title="Bandwidth Report: %s - %s" % (start_date, end_date)) + + sum_table = formatting.Table(['Type','Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") + + bw_totals = [ + {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, + {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, + {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, + {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, + ] + from pprint import pprint as pp + + + for point in formatted_data: + new_row = [point] + for bw_type in bw_totals: + counter = formatted_data[point].get(bw_type['keyName'], 0) + new_row.append(mb_to_gb(counter)) + bw_type['sum'] = bw_type['sum'] + counter + if counter > bw_type['max']: + bw_type['max'] = counter + bw_type['maxDate'] = point + table.add_row(new_row) + + for bw_type in bw_totals: + total = bw_type.get('sum', 0) + average = 0 + if total > 0: + average = round(total / len(formatted_data) / summary_period,4) + sum_table.add_row([ + bw_type.get('name'), + mb_to_gb(total), + average, + mb_to_gb(bw_type.get('max')), + bw_type.get('maxDate') + ]) + + env.fout(sum_table) + if not quite_summary: + env.fout(table) + + +def mb_to_gb(x): + return round(x / 2 ** 10, 4) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 7a418dadb..7d61d0b0e 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -82,6 +82,19 @@ def cli(env, identifier, passwords=False, price=False): vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) table.add_row(['vlans', vlan_table]) + bandwidth = vsi.get_bandwidth_allocation(vs_id) + bw_table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) + for bw in bandwidth.get('useage'): + bw_type = 'Private' + allotment = 'N/A' + if bw['type']['alias'] == 'PUBLIC_SERVER_BW': + bw_type = 'Public' + allotment = bandwidth['allotment'].get('amount', '-') + + bw_table.add_row([bw_type, bw['amountIn'], bw['amountOut'], allotment]) + table.add_row(['Bandwidth', bw_table]) + + if result.get('networkComponents'): secgroup_table = formatting.Table(['interface', 'id', 'name']) has_secgroups = False diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b66e3a875..e3a6fc1d6 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -686,8 +686,8 @@ def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direct :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. """ tracking_id = self.get_tracking_id(instance_id) - data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, rollup, - id=tracking_id) + data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, + rollup, id=tracking_id, iter=True) return data def get_bandwidth_allocation(self, instance_id): diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 073b8aeab..06bc77e8d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1018,11 +1018,45 @@ def get_summary_data_usage(self, instance_id, start_date=None, end_date=None, va } ] - metric_tracking_id = self.guest.getMetricTrackingObjectId(id=instance_id) + metric_tracking_id = self.get_tracking_id(instance_id) return self.client.call('Metric_Tracking_Object', 'getSummaryData', start_date, end_date, valid_types, summary_period, id=metric_tracking_id, iter=True) + def get_tracking_id(self, instance_id): + """Returns the Metric Tracking Object Id for a hardware server + + :param int instance_id: Id of the hardware server + """ + return self.guest.getMetricTrackingObjectId(id=instance_id) + + def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direction=None, rollup=3600): + """Gets bandwidth data for a server + + Will get averaged bandwidth data for a given time period. If you use a rollup over 3600 be aware + that the API will bump your start/end date to align with how data is stored. For example if you + have a rollup of 86400 your start_date will be bumped to 00:00. If you are not using a time in the + start/end date fields, this won't really matter. + + :param int instance_id: Hardware Id to get data for + :param date start_date: Date to start pulling data for. + :param date end_date: Date to finish pulling data for + :param string direction: Can be either 'public', 'private', or None for both. + :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. + """ + tracking_id = self.get_tracking_id(instance_id) + data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, + rollup, id=tracking_id, iter=True) + return data + + def get_bandwidth_allocation(self, instance_id): + """Combines getBandwidthAllotmentDetail() and getBillingCycleBandwidthUsage() """ + a_mask="mask[allocation[amount]]" + allotment = self.client.call('Virtual_Guest', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) + u_mask="mask[amountIn,amountOut,type]" + useage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) + return {'allotment': allotment['allocation'], 'useage': useage} + # pylint: disable=inconsistent-return-statements def _get_price_id_for_upgrade(self, package_items, option, value, public=True): """Find the price id for the option and value to upgrade. From f11a7e6f65704fca8089cbf50577ac39a5dd4080 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Fri, 17 May 2019 11:01:29 -0500 Subject: [PATCH 0091/1582] Remove tests, as making them work compatibly across python versions isn't working very well. --- tests/CLI/modules/shell_tests.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 tests/CLI/modules/shell_tests.py diff --git a/tests/CLI/modules/shell_tests.py b/tests/CLI/modules/shell_tests.py deleted file mode 100644 index d082a38db..000000000 --- a/tests/CLI/modules/shell_tests.py +++ /dev/null @@ -1,29 +0,0 @@ -""" - SoftLayer.tests.CLI.modules.shell_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -from SoftLayer import testing - -import tempfile - - -class ShellTests(testing.TestCase): - - def test_shell_quit(self): - # Use a file as stdin - with tempfile.NamedTemporaryFile() as stdin: - stdin.write(b'exit\n') - stdin.seek(0) - result = self.run_command(['shell'], stdin=stdin) - self.assertEqual(result.exit_code, 0) - - def test_shell_help(self): - # Use a file as stdin - with tempfile.NamedTemporaryFile() as stdin: - stdin.write(b'help\nexit\n') - stdin.seek(0) - result = self.run_command(['shell'], stdin=stdin) - self.assertIn('Welcome to the SoftLayer shell.', result.output) - self.assertEqual(result.exit_code, 0) From b3398745b88a3b7e07269a82008dc4421e0f0283 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 20 May 2019 15:21:25 -0500 Subject: [PATCH 0092/1582] tox analysis fixes --- SoftLayer/CLI/hardware/bandwidth.py | 21 ++--- SoftLayer/CLI/hardware/detail.py | 24 +++--- SoftLayer/CLI/virt/bandwidth.py | 23 +++--- SoftLayer/CLI/virt/detail.py | 115 ++++++++++++++++------------ SoftLayer/managers/hardware.py | 8 +- SoftLayer/managers/vs.py | 8 +- 6 files changed, 110 insertions(+), 89 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 256446abb..28186140c 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -14,7 +14,7 @@ @click.argument('identifier') @click.option('--start_date', '-s', type=click.STRING, required=True, help="Start Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss,") -@click.option('--end_date', '-e', type=click.STRING, required=True, +@click.option('--end_date', '-e', type=click.STRING, required=True, help="End Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss") @click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, help="300, 600, 1800, 3600, 43200 or 86400 seconds") @@ -23,7 +23,7 @@ @environment.pass_env def cli(env, identifier, start_date, end_date, summary_period, quite_summary): """Bandwidth data over date range. Bandwidth is listed in GB - + Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. @@ -39,7 +39,7 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): for point in data: key = utils.clean_time(point['dateTime']) data_type = point['type'] - value = round(point['counter'] / 2 ** 30,4) + value = round(point['counter'] / 2 ** 30, 4) if formatted_data.get(key) is None: formatted_data[key] = {} formatted_data[key][data_type] = value @@ -47,12 +47,12 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], title="Bandwidth Report: %s - %s" % (start_date, end_date)) - sum_table = formatting.Table(['Type','Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") + sum_table = formatting.Table(['Type', 'Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") bw_totals = [ - {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, - {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, - {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, + {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, + {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, + {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, ] for point in formatted_data: @@ -70,7 +70,7 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): total = bw_type.get('sum', 0) average = 0 if total > 0: - average = round(total / len(formatted_data) / summary_period,4) + average = round(total / len(formatted_data) / summary_period, 4) sum_table.add_row([ bw_type.get('name'), mb_to_gb(total), @@ -84,5 +84,6 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): env.fout(table) -def mb_to_gb(x): - return round(x / 2 ** 10, 4) \ No newline at end of file +def mb_to_gb(mbytes): + """Converts a MegaByte int to GigaByte. mbytes/2^10""" + return round(mbytes / 2 ** 10, 4) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d52b7b4d0..e254ad33e 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -58,15 +58,7 @@ def cli(env, identifier, passwords, price): table.add_row(['vlans', vlan_table]) bandwidth = hardware.get_bandwidth_allocation(hardware_id) - bw_table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) - for bw in bandwidth.get('useage'): - bw_type = 'Private' - allotment = 'N/A' - if bw['type']['alias'] == 'PUBLIC_SERVER_BW': - bw_type = 'Public' - allotment = bandwidth['allotment'].get('amount', '-') - - bw_table.add_row([bw_type, bw['amountIn'], bw['amountOut'], allotment]) + bw_table = _bw_table(bandwidth) table.add_row(['Bandwidth', bw_table]) if result.get('notes'): @@ -97,3 +89,17 @@ def cli(env, identifier, passwords, price): table.add_row(['tags', formatting.tags(result['tagReferences'])]) env.fout(table) + + +def _bw_table(bw_data): + """Generates a bandwidth useage table""" + table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) + for bw_point in bw_data.get('useage'): + bw_type = 'Private' + allotment = 'N/A' + if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': + bw_type = 'Public' + allotment = bw_data['allotment'].get('amount', '-') + + table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) + return table diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index cc657c7f1..03f6694e1 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -14,7 +14,7 @@ @click.argument('identifier') @click.option('--start_date', '-s', type=click.STRING, required=True, help="Start Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss,") -@click.option('--end_date', '-e', type=click.STRING, required=True, +@click.option('--end_date', '-e', type=click.STRING, required=True, help="End Date YYYY-MM-DD, YYYY-MM-DDTHH:mm:ss") @click.option('--summary_period', '-p', type=click.INT, default=3600, show_default=True, help="300, 600, 1800, 3600, 43200 or 86400 seconds") @@ -23,7 +23,7 @@ @environment.pass_env def cli(env, identifier, start_date, end_date, summary_period, quite_summary): """Bandwidth data over date range. Bandwidth is listed in GB - + Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. @@ -40,7 +40,7 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): key = utils.clean_time(point['dateTime']) data_type = point['type'] # conversion from byte to megabyte - value = round(point['counter'] / 2 ** 20,4) + value = round(point['counter'] / 2 ** 20, 4) if formatted_data.get(key) is None: formatted_data[key] = {} formatted_data[key][data_type] = value @@ -48,16 +48,14 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], title="Bandwidth Report: %s - %s" % (start_date, end_date)) - sum_table = formatting.Table(['Type','Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") + sum_table = formatting.Table(['Type', 'Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") bw_totals = [ - {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, - {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, - {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, + {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, + {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, + {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, ] - from pprint import pprint as pp - for point in formatted_data: new_row = [point] @@ -74,7 +72,7 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): total = bw_type.get('sum', 0) average = 0 if total > 0: - average = round(total / len(formatted_data) / summary_period,4) + average = round(total / len(formatted_data) / summary_period, 4) sum_table.add_row([ bw_type.get('name'), mb_to_gb(total), @@ -88,5 +86,6 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): env.fout(table) -def mb_to_gb(x): - return round(x / 2 ** 10, 4) +def mb_to_gb(mbytes): + """Converts a MegaByte int to GigaByte. mbytes/2^10""" + return round(mbytes / 2 ** 10, 4) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 7d61d0b0e..f51d2e49d 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -39,78 +39,42 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['domain', result['domain']]) table.add_row(['fqdn', result['fullyQualifiedDomainName']]) table.add_row(['status', formatting.FormattedItem( - result['status']['keyName'] or formatting.blank(), - result['status']['name'] or formatting.blank() + result['status']['keyName'], + result['status']['name'] )]) table.add_row(['state', formatting.FormattedItem( utils.lookup(result, 'powerState', 'keyName'), utils.lookup(result, 'powerState', 'name'), )]) table.add_row(['active_transaction', formatting.active_txn(result)]) - table.add_row(['datacenter', - result['datacenter']['name'] or formatting.blank()]) + table.add_row(['datacenter', result['datacenter']['name'] or formatting.blank()]) _cli_helper_dedicated_host(env, result, table) operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} - table.add_row(['os', operating_system.get('name') or formatting.blank()]) - table.add_row(['os_version', - operating_system.get('version') or formatting.blank()]) + table.add_row(['os', operating_system.get('name', '-')]) + table.add_row(['os_version', operating_system.get('version', '-')]) table.add_row(['cores', result['maxCpu']]) table.add_row(['memory', formatting.mb_to_gb(result['maxMemory'])]) - table.add_row(['public_ip', - result['primaryIpAddress'] or formatting.blank()]) - table.add_row(['private_ip', - result['primaryBackendIpAddress'] or formatting.blank()]) + table.add_row(['public_ip', result.get('primaryIpAddress', '-')]) + table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - if utils.lookup(result, 'billingItem') != []: - table.add_row(['owner', formatting.FormattedItem( - utils.lookup(result, 'billingItem', 'orderItem', - 'order', 'userRecord', - 'username') or formatting.blank(), - )]) - else: - table.add_row(['owner', formatting.blank()]) - vlan_table = formatting.Table(['type', 'number', 'id']) - for vlan in result['networkVlans']: - vlan_table.add_row([ - vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) - table.add_row(['vlans', vlan_table]) + table.add_row(_get_owner_row(result)) + table.add_row(_get_vlan_table(result)) bandwidth = vsi.get_bandwidth_allocation(vs_id) - bw_table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) - for bw in bandwidth.get('useage'): - bw_type = 'Private' - allotment = 'N/A' - if bw['type']['alias'] == 'PUBLIC_SERVER_BW': - bw_type = 'Public' - allotment = bandwidth['allotment'].get('amount', '-') + table.add_row(['Bandwidth', _bw_table(bandwidth)]) - bw_table.add_row([bw_type, bw['amountIn'], bw['amountOut'], allotment]) - table.add_row(['Bandwidth', bw_table]) + security_table = _get_security_table(result) + if security_table is not None: + table.add_row(['security_groups', security_table]) - - if result.get('networkComponents'): - secgroup_table = formatting.Table(['interface', 'id', 'name']) - has_secgroups = False - for comp in result.get('networkComponents'): - interface = 'PRIVATE' if comp['port'] == 0 else 'PUBLIC' - for binding in comp['securityGroupBindings']: - has_secgroups = True - secgroup = binding['securityGroup'] - secgroup_table.add_row([ - interface, secgroup['id'], - secgroup.get('name') or formatting.blank()]) - if has_secgroups: - table.add_row(['security_groups', secgroup_table]) - - if result.get('notes'): - table.add_row(['notes', result['notes']]) + table.add_row(['notes', result.get('notes', '-')]) if price: total_price = utils.lookup(result, @@ -158,6 +122,20 @@ def cli(env, identifier, passwords=False, price=False): env.fout(table) +def _bw_table(bw_data): + """Generates a bandwidth useage table""" + table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) + for bw_point in bw_data.get('useage'): + bw_type = 'Private' + allotment = 'N/A' + if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': + bw_type = 'Public' + allotment = bw_data['allotment'].get('amount', '-') + + table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) + return table + + def _cli_helper_dedicated_host(env, result, table): """Get details on dedicated host for a virtual server.""" @@ -173,3 +151,40 @@ def _cli_helper_dedicated_host(env, result, table): dedicated_host = {} table.add_row(['dedicated_host', dedicated_host.get('name') or formatting.blank()]) + + +def _get_owner_row(result): + """Formats and resturns the Owner row""" + + if utils.lookup(result, 'billingItem') != []: + owner = utils.lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username') + else: + owner = formatting.blank() + return(['owner', owner]) + + +def _get_vlan_table(result): + """Formats and resturns a vlan table""" + + vlan_table = formatting.Table(['type', 'number', 'id']) + for vlan in result['networkVlans']: + vlan_table.add_row([ + vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) + return vlan_table + + +def _get_security_table(result): + secgroup_table = formatting.Table(['interface', 'id', 'name']) + has_secgroups = False + + if result.get('networkComponents'): + for comp in result.get('networkComponents'): + interface = 'PRIVATE' if comp['port'] == 0 else 'PUBLIC' + for binding in comp['securityGroupBindings']: + has_secgroups = True + secgroup = binding['securityGroup'] + secgroup_table.add_row([interface, secgroup['id'], secgroup.get('name', '-')]) + if has_secgroups: + return secgroup_table + else: + return None diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e3a6fc1d6..4a52a5236 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -683,18 +683,18 @@ def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direct :param date start_date: Date to start pulling data for. :param date end_date: Date to finish pulling data for :param string direction: Can be either 'public', 'private', or None for both. - :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. + :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. """ tracking_id = self.get_tracking_id(instance_id) - data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, + data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, direction, rollup, id=tracking_id, iter=True) return data def get_bandwidth_allocation(self, instance_id): """Combines getBandwidthAllotmentDetail() and getBillingCycleBandwidthUsage() """ - a_mask="mask[allocation[amount]]" + a_mask = "mask[allocation[amount]]" allotment = self.client.call('Hardware_Server', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) - u_mask="mask[amountIn,amountOut,type]" + u_mask = "mask[amountIn,amountOut,type]" useage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) return {'allotment': allotment['allocation'], 'useage': useage} diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 06bc77e8d..03ac6d035 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1042,18 +1042,18 @@ def get_bandwidth_data(self, instance_id, start_date=None, end_date=None, direct :param date start_date: Date to start pulling data for. :param date end_date: Date to finish pulling data for :param string direction: Can be either 'public', 'private', or None for both. - :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. + :param int rollup: 300, 600, 1800, 3600, 43200 or 86400 seconds to average data over. """ tracking_id = self.get_tracking_id(instance_id) - data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, None, + data = self.client.call('Metric_Tracking_Object', 'getBandwidthData', start_date, end_date, direction, rollup, id=tracking_id, iter=True) return data def get_bandwidth_allocation(self, instance_id): """Combines getBandwidthAllotmentDetail() and getBillingCycleBandwidthUsage() """ - a_mask="mask[allocation[amount]]" + a_mask = "mask[allocation[amount]]" allotment = self.client.call('Virtual_Guest', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) - u_mask="mask[amountIn,amountOut,type]" + u_mask = "mask[amountIn,amountOut,type]" useage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) return {'allotment': allotment['allocation'], 'useage': useage} From 859a7f108a639ae8ceae6bfa34dc54f2dd4691e5 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 20 May 2019 16:02:18 -0500 Subject: [PATCH 0093/1582] getting unit tests running after changing a bunch of vs/hw detail stuff --- SoftLayer/CLI/virt/detail.py | 2 +- SoftLayer/decoration.py | 1 - .../fixtures/SoftLayer_Hardware_Server.py | 29 +++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 31 ++++++++ tests/CLI/modules/server_tests.py | 39 +++------- tests/CLI/modules/vs/vs_tests.py | 78 +++---------------- .../managers/vs/vs_waiting_for_ready_tests.py | 2 +- 7 files changed, 82 insertions(+), 100 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index f51d2e49d..feb03cf82 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -170,7 +170,7 @@ def _get_vlan_table(result): for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) - return vlan_table + return ['vlans', vlan_table] def _get_security_table(result): diff --git a/SoftLayer/decoration.py b/SoftLayer/decoration.py index 66ce62ccb..a5e35d740 100644 --- a/SoftLayer/decoration.py +++ b/SoftLayer/decoration.py @@ -15,7 +15,6 @@ exceptions.ServerError, exceptions.ApplicationError, exceptions.RemoteSystemError, - exceptions.TransportError ) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 72838692e..add899b50 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -118,3 +118,32 @@ } } ] + +getBandwidthAllotmentDetail = { + 'allocationId': 25465663, + 'bandwidthAllotmentId': 138442, + 'effectiveDate': '2019-04-03T23:00:00-06:00', + 'endEffectiveDate': None, + 'id': 25888247, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '250' + } +} + +getBillingCycleBandwidthUsage = [ + { + 'amountIn': '.448', + 'amountOut': '.52157', + 'type': { + 'alias': 'PUBLIC_SERVER_BW' + } + }, + { + 'amountIn': '.03842', + 'amountOut': '.01822', + 'type': { + 'alias': 'PRIVATE_SERVER_BW' + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 49433b01f..c65245855 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -628,3 +628,34 @@ ] getMetricTrackingObjectId = 1000 + + +getBandwidthAllotmentDetail = { + 'allocationId': 25465663, + 'bandwidthAllotmentId': 138442, + 'effectiveDate': '2019-04-03T23:00:00-06:00', + 'endEffectiveDate': None, + 'id': 25888247, + 'serviceProviderId': 1, + 'allocation': { + 'amount': '250' + } +} + +getBillingCycleBandwidthUsage = [ + { + 'amountIn': '.448', + 'amountOut': '.52157', + 'type': { + 'alias': 'PUBLIC_SERVER_BW' + } + }, + { + 'amountIn': '.03842', + 'amountOut': '.01822', + 'type': { + 'alias': 'PRIVATE_SERVER_BW' + } + } +] + diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 75e2cd78f..13cacb9b9 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -96,37 +96,18 @@ def test_server_credentials_exception_password_not_found(self): ) def test_server_details(self): - result = self.run_command(['server', 'detail', '1234', - '--passwords', '--price']) - expected = { - 'cores': 2, - 'created': '2013-08-01 15:23:45', - 'datacenter': 'TEST00', - 'guid': '1a2b3c-1701', - 'domain': 'test.sftlyr.ws', - 'hostname': 'hardware-test1', - 'fqdn': 'hardware-test1.test.sftlyr.ws', - 'id': 1000, - 'ipmi_ip': '10.1.0.3', - 'memory': 2048, - 'notes': 'These are test notes.', - 'os': 'Ubuntu', - 'os_version': 'Ubuntu 12.04 LTS', - 'owner': 'chechu', - 'prices': [{'Item': 'Total', 'Recurring Price': 16.08}, - {'Item': 'test', 'Recurring Price': 1}], - 'private_ip': '10.1.0.2', - 'public_ip': '172.16.1.100', - 'remote users': [{'password': 'abc123', 'ipmi_username': 'root'}], - 'status': 'ACTIVE', - 'tags': ['test_tag'], - 'users': [{'password': 'abc123', 'username': 'root'}], - 'vlans': [{'id': 9653, 'number': 1800, 'type': 'PRIVATE'}, - {'id': 19082, 'number': 3672, 'type': 'PUBLIC'}] - } + result = self.run_command(['server', 'detail', '1234', '--passwords', '--price']) self.assert_no_fail(result) - self.assertEqual(expected, json.loads(result.output)) + output = json.loads(result.output) + self.assertEqual(output['notes'], 'These are test notes.') + self.assertEqual(output['prices'][0]['Recurring Price'], 16.08) + self.assertEqual(output['remote users'][0]['password'], 'abc123') + self.assertEqual(output['users'][0]['username'], 'root') + self.assertEqual(output['vlans'][0]['number'], 1800) + self.assertEqual(output['owner'], 'chechu') + self.assertEqual(output['Bandwidth'][0]['Allotment'], '250') + def test_detail_vs_empty_tag(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 7b03bb084..4aa784338 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -168,78 +168,20 @@ def mock_lookup_func(dic, key, *keys): result = self.run_command(['vs', 'detail', '100', '--passwords', '--price']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'active_transaction': None, - 'cores': 2, - 'created': '2013-08-01 15:23:45', - 'datacenter': 'TEST00', - 'dedicated_host': 'test-dedicated', - 'dedicated_host_id': 37401, - 'hostname': 'vs-test1', - 'domain': 'test.sftlyr.ws', - 'fqdn': 'vs-test1.test.sftlyr.ws', - 'id': 100, - 'guid': '1a2b3c-1701', - 'memory': 1024, - 'modified': {}, - 'os': 'Ubuntu', - 'os_version': '12.04-64 Minimal for VSI', - 'notes': 'notes', - 'price_rate': 0, - 'tags': ['production'], - 'private_cpu': {}, - 'private_ip': '10.45.19.37', - 'private_only': {}, - 'ptr': 'test.softlayer.com.', - 'public_ip': '172.16.240.2', - 'state': 'RUNNING', - 'status': 'ACTIVE', - 'users': [{'software': 'Ubuntu', - 'password': 'pass', - 'username': 'user'}], - 'vlans': [{'type': 'PUBLIC', - 'number': 23, - 'id': 1}], - 'owner': None}) + output = json.loads(result.output) + self.assertEqual(output['owner'], None) def test_detail_vs(self): - result = self.run_command(['vs', 'detail', '100', - '--passwords', '--price']) + result = self.run_command(['vs', 'detail', '100', '--passwords', '--price']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'active_transaction': None, - 'cores': 2, - 'created': '2013-08-01 15:23:45', - 'datacenter': 'TEST00', - 'dedicated_host': 'test-dedicated', - 'dedicated_host_id': 37401, - 'hostname': 'vs-test1', - 'domain': 'test.sftlyr.ws', - 'fqdn': 'vs-test1.test.sftlyr.ws', - 'id': 100, - 'guid': '1a2b3c-1701', - 'memory': 1024, - 'modified': {}, - 'os': 'Ubuntu', - 'os_version': '12.04-64 Minimal for VSI', - 'notes': 'notes', - 'price_rate': 6.54, - 'tags': ['production'], - 'private_cpu': {}, - 'private_ip': '10.45.19.37', - 'private_only': {}, - 'ptr': 'test.softlayer.com.', - 'public_ip': '172.16.240.2', - 'state': 'RUNNING', - 'status': 'ACTIVE', - 'users': [{'software': 'Ubuntu', - 'password': 'pass', - 'username': 'user'}], - 'vlans': [{'type': 'PUBLIC', - 'number': 23, - 'id': 1}], - 'owner': 'chechu'}) + output = json.loads(result.output) + self.assertEqual(output['notes'], 'notes') + self.assertEqual(output['price_rate'], 6.54) + self.assertEqual(output['users'][0]['username'], 'user') + self.assertEqual(output['vlans'][0]['number'], 23) + self.assertEqual(output['owner'], 'chechu') + self.assertEqual(output['Bandwidth'][0]['Allotment'], '250') def test_detail_vs_empty_tag(self): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') diff --git a/tests/managers/vs/vs_waiting_for_ready_tests.py b/tests/managers/vs/vs_waiting_for_ready_tests.py index 802b945fd..4308bd55d 100644 --- a/tests/managers/vs/vs_waiting_for_ready_tests.py +++ b/tests/managers/vs/vs_waiting_for_ready_tests.py @@ -151,7 +151,7 @@ def test_exception_from_api(self, _sleep, _time, _vs, _dsleep): _dsleep.return_value = False self.guestObject.side_effect = [ - exceptions.TransportError(104, "Its broken"), + exceptions.ServerError(504, "Its broken"), {'activeTransaction': {'id': 1}}, {'provisionDate': 'aaa'} ] From a8baf6b28a60d3be0f428af30cb63bd029b29c01 Mon Sep 17 00:00:00 2001 From: "Albert J. Camacho" Date: Mon, 20 May 2019 18:47:42 -0400 Subject: [PATCH 0094/1582] #1147 removed contents from the new_tickets and unittests --- SoftLayer/managers/ticket.py | 1 - tests/CLI/modules/ticket_tests.py | 4 ---- tests/managers/ticket_tests.py | 1 - 3 files changed, 6 deletions(-) diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py index 6c1eb042f..04f8470b0 100644 --- a/SoftLayer/managers/ticket.py +++ b/SoftLayer/managers/ticket.py @@ -68,7 +68,6 @@ def create_ticket(self, title=None, body=None, subject=None, priority=None): current_user = self.account.getCurrentUser() new_ticket = { 'subjectId': subject, - 'contents': body, 'assignedUserId': current_user['id'], 'title': title, } diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 657953c5e..3f338cf1c 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -55,7 +55,6 @@ def test_create(self): self.assert_no_fail(result) args = ({'subjectId': 1000, - 'contents': 'ticket body', 'assignedUserId': 12345, 'title': 'Test'}, 'ticket body') @@ -70,7 +69,6 @@ def test_create_with_priority(self): self.assert_no_fail(result) args = ({'subjectId': 1000, - 'contents': 'ticket body', 'assignedUserId': 12345, 'title': 'Test', 'priority': 1}, 'ticket body') @@ -87,7 +85,6 @@ def test_create_and_attach(self): self.assert_no_fail(result) args = ({'subjectId': 1000, - 'contents': 'ticket body', 'assignedUserId': 12345, 'title': 'Test'}, 'ticket body') @@ -108,7 +105,6 @@ def test_create_no_body(self, edit_mock): self.assert_no_fail(result) args = ({'subjectId': 1000, - 'contents': 'ticket body', 'assignedUserId': 12345, 'title': 'Test'}, 'ticket body') diff --git a/tests/managers/ticket_tests.py b/tests/managers/ticket_tests.py index 50ed7b29a..ef3638f05 100644 --- a/tests/managers/ticket_tests.py +++ b/tests/managers/ticket_tests.py @@ -72,7 +72,6 @@ def test_create_ticket(self): subject=1004) args = ({"assignedUserId": 12345, - "contents": "body", "subjectId": 1004, "title": "Cloud Instance Cancellation - 08/01/13"}, "body") From 861ffab0b0439a02c684b140fa4a7bf5c1e19f06 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 22 May 2019 17:33:09 -0500 Subject: [PATCH 0095/1582] unit tests --- SoftLayer/CLI/hardware/bandwidth.py | 46 +---------- SoftLayer/CLI/virt/bandwidth.py | 20 +++-- .../fixtures/SoftLayer_Hardware_Server.py | 2 + .../SoftLayer_Metric_Tracking_Object.py | 45 +++++++++++ tests/CLI/modules/server_tests.py | 32 ++++++++ tests/CLI/modules/vs/vs_tests.py | 78 +++++++++++++++++++ 6 files changed, 174 insertions(+), 49 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 28186140c..3c1683145 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer.CLI.virt.bandwidth import create_bandwidth_table from SoftLayer import utils @@ -35,49 +36,8 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') data = hardware.get_bandwidth_data(hardware_id, start_date, end_date, None, summary_period) - formatted_data = {} - for point in data: - key = utils.clean_time(point['dateTime']) - data_type = point['type'] - value = round(point['counter'] / 2 ** 30, 4) - if formatted_data.get(key) is None: - formatted_data[key] = {} - formatted_data[key][data_type] = value - - table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], - title="Bandwidth Report: %s - %s" % (start_date, end_date)) - - sum_table = formatting.Table(['Type', 'Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") - - bw_totals = [ - {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, - {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, - {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, - {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, - ] - for point in formatted_data: - new_row = [point] - for bw_type in bw_totals: - counter = formatted_data[point].get(bw_type['keyName'], 0) - new_row.append(mb_to_gb(counter)) - bw_type['sum'] = bw_type['sum'] + counter - if counter > bw_type['max']: - bw_type['max'] = counter - bw_type['maxDate'] = point - table.add_row(new_row) - - for bw_type in bw_totals: - total = bw_type.get('sum', 0) - average = 0 - if total > 0: - average = round(total / len(formatted_data) / summary_period, 4) - sum_table.add_row([ - bw_type.get('name'), - mb_to_gb(total), - average, - mb_to_gb(bw_type.get('max')), - bw_type.get('maxDate') - ]) + title = "Bandwidth Report: %s - %s" % (start_date, end_date) + table, sum_table = create_bandwidth_table(data, summary_period, title) env.fout(sum_table) if not quite_summary: diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 03f6694e1..99f8aec18 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -35,6 +35,17 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') data = vsi.get_bandwidth_data(vsi_id, start_date, end_date, None, summary_period) + title = "Bandwidth Report: %s - %s" % (start_date, end_date) + table, sum_table = create_bandwidth_table(data, summary_period, title) + + + env.fout(sum_table) + if not quite_summary: + env.fout(table) + +def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): + """Create 2 tables, bandwidth and sumamry. Used here and in hw bandwidth command""" + formatted_data = {} for point in data: key = utils.clean_time(point['dateTime']) @@ -45,11 +56,11 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): formatted_data[key] = {} formatted_data[key][data_type] = value - table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], - title="Bandwidth Report: %s - %s" % (start_date, end_date)) + table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], title=title) sum_table = formatting.Table(['Type', 'Sum GB', 'Average MBps', 'Max GB', 'Max Date'], title="Summary") + # Required to specify keyName because getBandwidthTotals returns other counter types for some reason. bw_totals = [ {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, @@ -81,10 +92,7 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): bw_type.get('maxDate') ]) - env.fout(sum_table) - if not quite_summary: - env.fout(table) - + return table, sum_table def mb_to_gb(mbytes): """Converts a MegaByte int to GigaByte. mbytes/2^10""" diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index add899b50..47e9a1bcb 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -147,3 +147,5 @@ } } ] + +getMetricTrackingObjectId = 1000 diff --git a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py index 6a0a031a2..7f577c1a8 100644 --- a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py +++ b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py @@ -10,3 +10,48 @@ "type": "cpu0" }, ] + + +# Using counter > 32bit int causes unit tests to fail. +getBandwidthData =[ + { + 'counter': 37.21, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'cpu0' + }, + { + 'counter': 76.12, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'cpu1' + }, + { + 'counter': 257623973, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'memory' + }, + { + 'counter': 137118503, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'memory_usage' + }, + { + 'counter': 125888818, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'privateIn_net_octet' + }, + { + 'counter': 961037, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'privateOut_net_octet' + }, + { + 'counter': 1449885176, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'publicIn_net_octet' + }, + { + 'counter': 91803794, + 'dateTime': '2019-05-20T23:00:00-06:00', + 'type': 'publicOut_net_octet' + } +] diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 13cacb9b9..8384d4b44 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -584,3 +584,35 @@ def test_toggle_ipmi_off(self): result = self.run_command(['server', 'toggle-ipmi', '--disable', '12345']) self.assert_no_fail(result) self.assertEqual(result.output, 'True\n') + + def test_bandwidth_hw(self): + result = self.run_command(['server', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01']) + self.assert_no_fail(result) + + # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" + # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied + from pprint import pprint as pp + pp(result.output) + print("FUCK") + pp(result.output[0:-157]) + output_summary = json.loads(result.output[0:-157]) + output_list = json.loads(result.output[-158:]) + + self.assertEqual(output_summary[0]['Average MBps'], 0.3841) + self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[2]['Max GB'], 0.1172) + self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + self.assertEqual(output_list[0]['Date'], '2019-05-20 23:00') + self.assertEqual(output_list[0]['Pub In'], 1.3503) + + def test_bandwidth_hw_quite(self): + result = self.run_command(['server', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) + self.assert_no_fail(result) + output_summary = json.loads(result.output) + + self.assertEqual(output_summary[0]['Average MBps'], 0.3841) + self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[2]['Max GB'], 0.1172) + self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 4aa784338..e29dadd8c 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -9,6 +9,7 @@ import mock from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest from SoftLayer import SoftLayerAPIError from SoftLayer import testing @@ -220,6 +221,49 @@ def test_detail_vs_no_dedicated_host_hostname(self): self.assertEqual(json.loads(result.output)['dedicated_host_id'], 37401) self.assertIsNone(json.loads(result.output)['dedicated_host']) + def test_detail_vs_security_group(self): + vg_return = SoftLayer_Virtual_Guest.getObject + sec_group = [ + { + 'id': 35386715, + 'name': 'eth', + 'port': 0, + 'speed': 100, + 'status': 'ACTIVE', + 'primaryIpAddress': '10.175.106.149', + 'securityGroupBindings': [ + { + 'id': 1620971, + 'networkComponentId': 35386715, + 'securityGroupId': 128321, + 'securityGroup': { + 'id': 128321, + 'name': 'allow_all' + } + } + ] + } + ] + + vg_return['networkComponents'] = sec_group + 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) + output = json.loads(result.output) + self.assertEqual(output['security_groups'][0]['id'], 128321) + self.assertEqual(output['security_groups'][0]['name'], 'allow_all') + self.assertEqual(output['security_groups'][0]['interface'], 'PRIVATE') + + def test_detail_vs_ptr_error(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getReverseDomainRecords') + mock.side_effect = SoftLayerAPIError("SoftLayer_Exception", "Not Found") + result = self.run_command(['vs', 'detail', '100']) + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output.get('ptr', None), None) + + def test_create_options(self): result = self.run_command(['vs', 'create-options']) @@ -638,3 +682,37 @@ def test_usage_metric_data_empty(self): '--summary_period=300']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_bandwidth_vs(self): + result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01']) + self.assert_no_fail(result) + + # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" + # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied + + from pprint import pprint as pp + pp(result.output) + print("FUCK") + pp(result.output[0:-157]) + + output_summary = json.loads(result.output[0:-157]) + output_list = json.loads(result.output[-158:]) + + self.assertEqual(output_summary[0]['Average MBps'], 0.3841) + self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[2]['Max GB'], 0.1172) + self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + self.assertEqual(output_list[0]['Date'], '2019-05-20 23:00') + self.assertEqual(output_list[0]['Pub In'], 1.3503) + + def test_bandwidth_vs_quite(self): + result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) + self.assert_no_fail(result) + output_summary = json.loads(result.output) + + self.assertEqual(output_summary[0]['Average MBps'], 0.3841) + self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[2]['Max GB'], 0.1172) + self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + From 1b496b260ee1934c11f844045983c93d81322d4a Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 May 2019 12:50:14 -0400 Subject: [PATCH 0096/1582] #1139 Added subnet static option --- SoftLayer/CLI/subnet/create.py | 28 ++++++++++++++++++++-------- SoftLayer/managers/network.py | 30 +++++++++++++++++++++--------- tests/CLI/modules/subnet_tests.py | 21 ++++++++++++++++++++- tests/managers/network_tests.py | 6 +++--- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/subnet/create.py b/SoftLayer/CLI/subnet/create.py index 1844cf627..7d5d6d912 100644 --- a/SoftLayer/CLI/subnet/create.py +++ b/SoftLayer/CLI/subnet/create.py @@ -10,25 +10,33 @@ @click.command(short_help="Add a new subnet to your account") -@click.argument('network', type=click.Choice(['public', 'private'])) +@click.argument('network', type=click.Choice(['static', 'public', 'private'])) @click.argument('quantity', type=click.INT) -@click.argument('vlan-id') +@click.argument('endpoint-id', type=click.INT) @click.option('--ipv6', '--v6', is_flag=True, help="Order IPv6 Addresses") @click.option('--test', is_flag=True, help="Do not order the subnet; just get a quote") @environment.pass_env -def cli(env, network, quantity, vlan_id, ipv6, test): +def cli(env, network, quantity, endpoint_id, ipv6, test): """Add a new subnet to your account. Valid quantities vary by type. \b Type - Valid Quantities (IPv4) - public - 4, 8, 16, 32 - private - 4, 8, 16, 32, 64 + static - 1, 2, 4, 8, 16, 32, 64, 128, 256 + public - 4, 8, 16, 32, 64, 128, 256 + private - 4, 8, 16, 32, 64, 128, 256 \b Type - Valid Quantities (IPv6) + static - 64 public - 64 + + \b + Type - endpoint-id + static - IP address identifier. + public - VLAN identifier + private - VLAN identifier """ mgr = SoftLayer.NetworkManager(env.client) @@ -43,9 +51,13 @@ def cli(env, network, quantity, vlan_id, ipv6, test): version = 6 try: - result = mgr.add_subnet(network, quantity=quantity, vlan_id=vlan_id, version=version, test_order=test) - except SoftLayer.SoftLayerAPIError: - raise exceptions.CLIAbort('There is no price id for {} {} ipv{}'.format(quantity, network, version)) + result = mgr.add_subnet(network, quantity=quantity, endpoint_id=endpoint_id, version=version, test_order=test) + + except SoftLayer.SoftLayerAPIError as error: + raise exceptions.CLIAbort('Unable to order {} {} ipv{} , error: {}'.format(quantity, + network, + version, + error.faultString)) table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 5fb25ee09..cdbf86ba1 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -110,13 +110,13 @@ def add_securitygroup_rules(self, group_id, rules): raise TypeError("The rules provided must be a list of dictionaries") return self.security_group.addRules(rules, id=group_id) - def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, + def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, test_order=False): """Orders a new subnet - :param str subnet_type: Type of subnet to add: private, public, global + :param str subnet_type: Type of subnet to add: private, public, global,static :param int quantity: Number of IPs in the subnet - :param int vlan_id: VLAN id for the subnet to be placed into + :param int endpoint_id: id for the subnet to be placed into :param int version: 4 for IPv4, 6 for IPv6 :param bool test_order: If true, this will only verify the order. """ @@ -126,9 +126,11 @@ def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, if version == 4: if subnet_type == 'global': quantity = 0 - category = 'global_ipv4' + category = "global_ipv4" elif subnet_type == 'public': - category = 'sov_sec_ip_addresses_pub' + category = "sov_sec_ip_addresses_pub" + elif subnet_type == 'static': + category = "static_sec_ip_addresses" else: category = 'static_ipv6_addresses' if subnet_type == 'global': @@ -137,6 +139,8 @@ def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, desc = 'Global' elif subnet_type == 'public': desc = 'Portable' + elif subnet_type == 'static': + desc = 'Static' # In the API, every non-server item is contained within package ID 0. # This means that we need to get all of the items and loop through them @@ -144,8 +148,15 @@ def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, # item description. price_id = None quantity_str = str(quantity) - for item in package.getItems(id=0, mask='itemCategory'): + # package_items = package.getItems(id=0, mask='itemCategory') + package_items = package.getItems(id=0) + for item in package_items: category_code = utils.lookup(item, 'itemCategory', 'categoryCode') + # if (category_code == category + # and item.get('capacity') == quantity_str + # and (version == 4 or (version == 6 and desc in item['description']))): + # price_id = item['prices'][0]['id'] + # break if all([category_code == category, item.get('capacity') == quantity_str, version == 4 or (version == 6 and @@ -161,9 +172,10 @@ def add_subnet(self, subnet_type, quantity=None, vlan_id=None, version=4, # correct order container 'complexType': 'SoftLayer_Container_Product_Order_Network_Subnet', } - - if subnet_type != 'global': - order['endPointVlanId'] = vlan_id + if subnet_type == 'static': + order['endPointIpAddressId'] = endpoint_id + elif subnet_type != 'global' and subnet_type != 'static': + order['endPointVlanId'] = endpoint_id if test_order: return self.client['Product_Order'].verifyOrder(order) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index befc6a2e7..f55846995 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -94,4 +94,23 @@ def test_create_subnet_no_prices_found(self): result = self.run_command(['subnet', 'create', '--v6', 'public', '32', '12346', '--test']) self.assertRaises(SoftLayer.SoftLayerAPIError, verify_mock) - self.assertEqual(result.exception.message, 'There is no price id for 32 public ipv6') + self.assertIn('Unable to order 32 public ipv6', result.exception.message, ) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_subnet_static(self, confirm_mock): + confirm_mock.return_value = True + + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems + + place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + place_mock.return_value = SoftLayer_Product_Order.placeOrder + + result = self.run_command(['subnet', 'create', 'static', '2', '12346']) + self.assert_no_fail(result) + + output = [ + {'Item': 'Total monthly cost', 'cost': '0.00'} + ] + + self.assertEqual(output, json.loads(result.output)) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 812781c9b..a87441a99 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -81,7 +81,7 @@ def test_add_subnet_for_ipv4(self): # Test a four public address IPv4 order result = self.network.add_subnet('public', quantity=4, - vlan_id=1234, + endpoint_id=1234, version=4, test_order=True) @@ -89,7 +89,7 @@ def test_add_subnet_for_ipv4(self): result = self.network.add_subnet('public', quantity=4, - vlan_id=1234, + endpoint_id=1234, version=4, test_order=False) @@ -104,7 +104,7 @@ def test_add_subnet_for_ipv6(self): # Test a public IPv6 order result = self.network.add_subnet('public', quantity=64, - vlan_id=45678, + endpoint_id=45678, version=6, test_order=True) From 6ccaa27d993cf6353424cb0219529867fa34ad0e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 May 2019 16:41:23 -0500 Subject: [PATCH 0097/1582] finishing up unit tests --- SoftLayer/CLI/hardware/bandwidth.py | 5 - tests/CLI/modules/server_tests.py | 4 - tests/CLI/modules/vs/vs_tests.py | 6 - tests/managers/hardware_tests.py | 173 ++++++++++++++++++++++++++-- tests/managers/vs/vs_order_tests.py | 90 +++++++++++++-- tests/managers/vs/vs_tests.py | 41 +++++++ 6 files changed, 281 insertions(+), 38 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 3c1683145..da26998d1 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -42,8 +42,3 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): env.fout(sum_table) if not quite_summary: env.fout(table) - - -def mb_to_gb(mbytes): - """Converts a MegaByte int to GigaByte. mbytes/2^10""" - return round(mbytes / 2 ** 10, 4) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 8384d4b44..c9aebd04b 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -591,10 +591,6 @@ def test_bandwidth_hw(self): # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied - from pprint import pprint as pp - pp(result.output) - print("FUCK") - pp(result.output[0:-157]) output_summary = json.loads(result.output[0:-157]) output_list = json.loads(result.output[-158:]) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index e29dadd8c..f8365ae48 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -689,12 +689,6 @@ def test_bandwidth_vs(self): # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied - - from pprint import pprint as pp - pp(result.output) - print("FUCK") - pp(result.output[0:-157]) - output_summary = json.loads(result.output[0:-157]) output_list = json.loads(result.output[-158:]) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 030d808d6..895146fb2 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -10,6 +10,7 @@ import SoftLayer + from SoftLayer import fixtures from SoftLayer import managers from SoftLayer import testing @@ -320,6 +321,14 @@ def test_cancel_hardware_monthly_whenever(self): self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier=6327, args=(False, False, 'No longer needed', '')) + def test_cancel_running_transaction(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + mock.return_value = {'id': 987, 'billingItem': {'id': 6327}, + 'activeTransaction': {'id': 4567}} + self.assertRaises(SoftLayer.SoftLayerError, + self.hardware.cancel_hardware, + 12345) + def test_change_port_speed_public(self): self.hardware.change_port_speed(2, True, 100) @@ -410,14 +419,102 @@ def test_reflash_firmware_selective(self): 'createFirmwareReflashTransaction', identifier=100, args=(1, 0, 0)) + def test_get_tracking_id(self): + result = self.hardware.get_tracking_id(1234) + self.assert_called_with('SoftLayer_Hardware_Server', 'getMetricTrackingObjectId') + self.assertEqual(result, 1000) + + def test_get_bandwidth_data(self): + result = self.hardware.get_bandwidth_data(1234, '2019-01-01', '2019-02-01', 'public', 1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getBandwidthData', args=('2019-01-01', '2019-02-01', + 'public', 1000), identifier=1000) + self.assertEqual(result[0]['type'], 'cpu0') + + def test_get_bandwidth_allocation(self): + result = self.hardware.get_bandwidth_allocation(1234) + self.assert_called_with('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail', identifier=1234) + self.assert_called_with('SoftLayer_Hardware_Server', 'getBillingCycleBandwidthUsage', identifier=1234) + self.assertEqual(result['allotment']['amount'], '250') + self.assertEqual(result['useage'][0]['amountIn'], '.448') + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_extra_price_id, [], 'test', True, None) - self.assertEqual("Could not find valid price for extra option, 'test'", - str(ex)) + self.assertEqual("Could not find valid price for extra option, 'test'", str(ex)) + + def test_get_extra_price_mismatched(self): + items = [ + {'keyName': 'TEST', 'prices':[{'id':1, 'locationGroupId': None, 'recurringFee':99}]}, + {'keyName': 'TEST', 'prices':[{'id':2, 'locationGroupId': 55, 'hourlyRecurringFee':99}]}, + {'keyName': 'TEST', 'prices':[{'id':3, 'locationGroupId': None, 'hourlyRecurringFee':99}]}, + ] + location = { + 'location': { + 'location': { + 'priceGroups': [ + {'id': 50}, + {'id': 51} + ] + } + } + } + result = managers.hardware._get_extra_price_id(items, 'TEST', True, location) + self.assertEqual(3, result) + + def test_get_bandwidth_price_mismatched(self): + items = [ + {'itemCategory': {'categoryCode':'bandwidth'}, + 'capacity': 100, + 'prices':[{'id':1, 'locationGroupId': None, 'hourlyRecurringFee':99}] + }, + {'itemCategory': {'categoryCode':'bandwidth'}, + 'capacity': 100, + 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'bandwidth'}, + 'capacity': 100, + 'prices':[{'id':3, 'locationGroupId': None, 'recurringFee':99}] + }, + ] + location = { + 'location': { + 'location': { + 'priceGroups': [ + {'id': 50}, + {'id': 51} + ] + } + } + } + result = managers.hardware._get_bandwidth_price_id(items, False, False, location) + self.assertEqual(3, result) + + def test_get_os_price_mismatched(self): + items = [ + {'itemCategory': {'categoryCode':'os'}, + 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'os'}, + 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'prices':[{'id':3, 'locationGroupId': None, 'recurringFee':99}] + }, + ] + location = { + 'location': { + 'location': { + 'priceGroups': [ + {'id': 50}, + {'id': 51} + ] + } + } + } + result = managers.hardware._get_os_price_id(items, 'TEST_OS', location) + self.assertEqual(3, result) def test_get_default_price_id_item_not_first(self): items = [{ @@ -432,33 +529,85 @@ def test_get_default_price_id_item_not_first(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_default_price_id, items, 'unknown', True, None) - self.assertEqual("Could not find valid price for 'unknown' option", - str(ex)) + self.assertEqual("Could not find valid price for 'unknown' option", str(ex)) def test_get_default_price_id_no_items(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_default_price_id, [], 'test', True, None) - self.assertEqual("Could not find valid price for 'test' option", - str(ex)) + self.assertEqual("Could not find valid price for 'test' option", str(ex)) def test_get_bandwidth_price_id_no_items(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_bandwidth_price_id, [], hourly=True, no_public=False) - self.assertEqual("Could not find valid price for bandwidth option", - str(ex)) + self.assertEqual("Could not find valid price for bandwidth option", str(ex)) def test_get_os_price_id_no_items(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_os_price_id, [], 'UBUNTU_14_64', None) - self.assertEqual("Could not find valid price for os: 'UBUNTU_14_64'", - str(ex)) + self.assertEqual("Could not find valid price for os: 'UBUNTU_14_64'", str(ex)) def test_get_port_speed_price_id_no_items(self): ex = self.assertRaises(SoftLayer.SoftLayerError, managers.hardware._get_port_speed_price_id, [], 10, True, None) - self.assertEqual("Could not find valid price for port speed: '10'", - str(ex)) + self.assertEqual("Could not find valid price for port speed: '10'", str(ex)) + + def test_get_port_speed_price_id_mismatch(self): + items = [ + {'itemCategory': {'categoryCode':'port_speed'}, + 'capacity':101, + 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices':[{'id':1, 'locationGroupId': None, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'port_speed'}, + 'capacity':100, + 'attributes':[{'attributeTypeKeyName': 'IS_NOT_PRIVATE_NETWORK_ONLY'}], + 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'port_speed'}, + 'capacity':100, + 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}, {'attributeTypeKeyName': 'NON_LACP'}], + 'prices':[{'id':3, 'locationGroupId': 55, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'port_speed'}, + 'capacity':100, + 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices':[{'id':4, 'locationGroupId': 12, 'recurringFee':99}] + }, + {'itemCategory': {'categoryCode':'port_speed'}, + 'capacity':100, + 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices':[{'id':5, 'locationGroupId': None, 'recurringFee':99}] + }, + ] + location = { + 'location': { + 'location': { + 'priceGroups': [ + {'id': 50}, + {'id': 51} + ] + } + } + } + result = managers.hardware._get_port_speed_price_id(items, 100, True, location) + self.assertEqual(5, result) + + def test_matches_location(self): + price = {'id':1, 'locationGroupId': 51, 'recurringFee':99} + location = { + 'location': { + 'location': { + 'priceGroups': [ + {'id': 50}, + {'id': 51} + ] + } + } + } + result = managers.hardware._matches_location(price, location) + self.assertTrue(result) + diff --git a/tests/managers/vs/vs_order_tests.py b/tests/managers/vs/vs_order_tests.py index 12750224d..3a9b273e4 100644 --- a/tests/managers/vs/vs_order_tests.py +++ b/tests/managers/vs/vs_order_tests.py @@ -9,6 +9,7 @@ import mock import SoftLayer +from SoftLayer import exceptions from SoftLayer import fixtures from SoftLayer import testing @@ -45,16 +46,11 @@ def test_upgrade_blank(self): result = self.vs.upgrade(1) self.assertEqual(result, False) - self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), - []) + self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) def test_upgrade_full(self): # Testing all parameters Upgrade - result = self.vs.upgrade(1, - cpus=4, - memory=2, - nic_speed=1000, - public=True) + result = self.vs.upgrade(1, cpus=4, memory=2, nic_speed=1000, public=True) self.assertEqual(result, True) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') @@ -67,10 +63,7 @@ def test_upgrade_full(self): def test_upgrade_with_flavor(self): # Testing Upgrade with parameter preset - result = self.vs.upgrade(1, - preset="M1_64X512X100", - nic_speed=1000, - public=True) + result = self.vs.upgrade(1, preset="M1_64X512X100", nic_speed=1000, public=True) self.assertEqual(result, True) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') @@ -141,6 +134,42 @@ def test_get_price_id_for_upgrade_finds_memory_price(self): value='1000') self.assertEqual(1122, price_id) + def test__get_price_id_for_upgrade_find_private_price(self): + package_items = self.vs._get_package_items() + price_id = self.vs._get_price_id_for_upgrade(package_items=package_items, + option='cpus', + value='4', + public=False) + self.assertEqual(1007, price_id) + + def test_upgrade_mem_and_preset_exception(self): + self.assertRaises( + ValueError, + self.vs.upgrade, + 1234, + memory=10, + preset="M1_64X512X100" + ) + + def test_upgrade_cpu_and_preset_exception(self): + self.assertRaises( + ValueError, + self.vs.upgrade, + 1234, + cpus=10, + preset="M1_64X512X100" + ) + + @mock.patch('SoftLayer.managers.vs.VSManager._get_price_id_for_upgrade_option') + def test_upgrade_no_price_exception(self, get_price): + get_price.return_value = None + self.assertRaises( + exceptions.SoftLayerError, + self.vs.upgrade, + 1234, + memory=1, + ) + @mock.patch('SoftLayer.managers.vs.VSManager._generate_create_dict') def test_order_guest(self, create_dict): create_dict.return_value = {'test': 1, 'verify': 1} @@ -173,3 +202,42 @@ def test_order_guest_ipv6(self, create_dict): self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate') self.assert_called_with('SoftLayer_Product_Package', 'getItems', identifier=200) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') + + @mock.patch('SoftLayer.managers.vs.VSManager._generate_create_dict') + def test_order_guest_placement_group(self, create_dict): + create_dict.return_value = {'test': 1, 'verify': 1} + guest = {'test': 1, 'verify': 1, 'placement_id': 5} + result = self.vs.order_guest(guest, test=True) + + call = self.calls('SoftLayer_Product_Order', 'verifyOrder')[0] + order_container = call.args[0] + + self.assertEqual(1234, result['orderId']) + self.assertEqual(5, order_container['virtualGuests'][0]['placementGroupId']) + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate') + self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') + + def test_get_price_id_empty(self): + upgrade_prices = [ + {'categories': None, 'item': None}, + {'categories': [{'categoryCode': 'ram'}], 'item': None}, + {'categories': None, 'item': {'capacity':1}}, + ] + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) + self.assertEqual(None,result) + + def test_get_price_id_memory_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':1},'id':99} + ] + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) + self.assertEqual(99,result) + + def test_get_price_id_mismatch_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity':1},'id':90}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':2},'id':91}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':1},'id':92}, + ] + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) + self.assertEqual(92,result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 47674345f..e892028c1 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -477,6 +477,28 @@ def test_generate_private_vlan(self): self.assertEqual(data, assert_data) + def test_generate_sec_group(self): + data = self.vs._generate_create_dict( + cpus=1, + memory=1, + hostname='test', + domain='test.com', + os_code="OS", + public_security_groups=[1,2,3], + private_security_groups=[4,5,6] + ) + + pub_sec_binding = data['primaryNetworkComponent']['securityGroupBindings'] + prv_sec_binding = data['primaryBackendNetworkComponent']['securityGroupBindings'] + # Public + self.assertEqual(pub_sec_binding[0]['securityGroup']['id'], 1) + self.assertEqual(pub_sec_binding[1]['securityGroup']['id'], 2) + self.assertEqual(pub_sec_binding[2]['securityGroup']['id'], 3) + # Private + self.assertEqual(prv_sec_binding[0]['securityGroup']['id'], 4) + self.assertEqual(prv_sec_binding[1]['securityGroup']['id'], 5) + self.assertEqual(prv_sec_binding[2]['securityGroup']['id'], 6) + def test_create_network_components_vlan_subnet_private_vlan_subnet_public(self): data = self.vs._create_network_components( private_vlan=1, @@ -858,3 +880,22 @@ def test_usage_vs_memory(self): args = ('2019-3-4', '2019-4-2', [{"keyName": "MEMORY_USAGE", "summaryType": "max"}], 300) self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', args=args, identifier=1000) + + def test_get_tracking_id(self): + result = self.vs.get_tracking_id(1234) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getMetricTrackingObjectId') + self.assertEqual(result, 1000) + + def test_get_bandwidth_data(self): + result = self.vs.get_bandwidth_data(1234, '2019-01-01', '2019-02-01', 'public', 1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getBandwidthData', args=('2019-01-01', '2019-02-01', + 'public', 1000), identifier=1000) + self.assertEqual(result[0]['type'], 'cpu0') + + def test_get_bandwidth_allocation(self): + result = self.vs.get_bandwidth_allocation(1234) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail', identifier=1234) + self.assert_called_with('SoftLayer_Virtual_Guest', 'getBillingCycleBandwidthUsage', identifier=1234) + self.assertEqual(result['allotment']['amount'], '250') + self.assertEqual(result['useage'][0]['amountIn'], '.448') + From 7f8c80512d342fdf0f639df1764b81da45c3b2af Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 May 2019 18:08:34 -0500 Subject: [PATCH 0098/1582] tox fixes --- SoftLayer/CLI/hardware/bandwidth.py | 2 - SoftLayer/CLI/virt/bandwidth.py | 17 +-- .../SoftLayer_Metric_Tracking_Object.py | 2 +- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 1 - tests/CLI/modules/server_tests.py | 31 +++-- tests/CLI/modules/vs/vs_tests.py | 35 ++++-- tests/managers/hardware_tests.py | 113 +++++++++--------- tests/managers/vs/vs_order_tests.py | 32 ++--- tests/managers/vs/vs_tests.py | 14 ++- 9 files changed, 141 insertions(+), 106 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index da26998d1..1984d8658 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -5,10 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers from SoftLayer.CLI.virt.bandwidth import create_bandwidth_table -from SoftLayer import utils @click.command() diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 99f8aec18..235d7f406 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -38,11 +38,11 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): title = "Bandwidth Report: %s - %s" % (start_date, end_date) table, sum_table = create_bandwidth_table(data, summary_period, title) - env.fout(sum_table) if not quite_summary: env.fout(table) + def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): """Create 2 tables, bandwidth and sumamry. Used here and in hw bandwidth command""" @@ -51,10 +51,10 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): key = utils.clean_time(point['dateTime']) data_type = point['type'] # conversion from byte to megabyte - value = round(point['counter'] / 2 ** 20, 4) + value = round(float(point['counter']) / 2 ** 20, 4) if formatted_data.get(key) is None: formatted_data[key] = {} - formatted_data[key][data_type] = value + formatted_data[key][data_type] = float(value) table = formatting.Table(['Date', 'Pub In', 'Pub Out', 'Pri In', 'Pri Out'], title=title) @@ -62,10 +62,10 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): # Required to specify keyName because getBandwidthTotals returns other counter types for some reason. bw_totals = [ - {'keyName': 'publicIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub In'}, - {'keyName': 'publicOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pub Out'}, - {'keyName': 'privateIn_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri In'}, - {'keyName': 'privateOut_net_octet', 'sum': 0, 'max': 0, 'name': 'Pri Out'}, + {'keyName': 'publicIn_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pub In'}, + {'keyName': 'publicOut_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pub Out'}, + {'keyName': 'privateIn_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pri In'}, + {'keyName': 'privateOut_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pri Out'}, ] for point in formatted_data: @@ -80,7 +80,7 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): table.add_row(new_row) for bw_type in bw_totals: - total = bw_type.get('sum', 0) + total = bw_type.get('sum', 0.0) average = 0 if total > 0: average = round(total / len(formatted_data) / summary_period, 4) @@ -94,6 +94,7 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): return table, sum_table + def mb_to_gb(mbytes): """Converts a MegaByte int to GigaByte. mbytes/2^10""" return round(mbytes / 2 ** 10, 4) diff --git a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py index 7f577c1a8..50cfb197a 100644 --- a/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py +++ b/SoftLayer/fixtures/SoftLayer_Metric_Tracking_Object.py @@ -13,7 +13,7 @@ # Using counter > 32bit int causes unit tests to fail. -getBandwidthData =[ +getBandwidthData = [ { 'counter': 37.21, 'dateTime': '2019-05-20T23:00:00-06:00', diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c65245855..aaae79b73 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -658,4 +658,3 @@ } } ] - diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index c9aebd04b..f14118bc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -108,7 +108,6 @@ def test_server_details(self): self.assertEqual(output['owner'], 'chechu') self.assertEqual(output['Bandwidth'][0]['Allotment'], '250') - def test_detail_vs_empty_tag(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') mock.return_value = { @@ -586,29 +585,45 @@ def test_toggle_ipmi_off(self): self.assertEqual(result.output, 'True\n') def test_bandwidth_hw(self): + if sys.version_info < (3, 6): + self.skipTest("Test requires python 3.6+") result = self.run_command(['server', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01']) self.assert_no_fail(result) + date = '2019-05-20 23:00' + # number of characters from the end of output to break so json can parse properly + pivot = 157 + # only pyhon 3.7 supports the timezone format slapi uses + if sys.version_info < (3, 7): + date = '2019-05-20T23:00:00-06:00' + pivot = 166 # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied - output_summary = json.loads(result.output[0:-157]) - output_list = json.loads(result.output[-158:]) + + output_summary = json.loads(result.output[0:-pivot]) + output_list = json.loads(result.output[-pivot:]) self.assertEqual(output_summary[0]['Average MBps'], 0.3841) - self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) - self.assertEqual(output_list[0]['Date'], '2019-05-20 23:00') + self.assertEqual(output_list[0]['Date'], date) self.assertEqual(output_list[0]['Pub In'], 1.3503) def test_bandwidth_hw_quite(self): - result = self.run_command(['server', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) + result = self.run_command(['server', 'bandwidth', '100', '--start_date=2019-01-01', + '--end_date=2019-02-01', '-q']) self.assert_no_fail(result) + date = '2019-05-20 23:00' + + # only pyhon 3.7 supports the timezone format slapi uses + if sys.version_info < (3, 7): + date = '2019-05-20T23:00:00-06:00' + output_summary = json.loads(result.output) self.assertEqual(output_summary[0]['Average MBps'], 0.3841) - self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) - diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index f8365ae48..cbb8ef209 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +import sys import mock @@ -261,8 +262,7 @@ def test_detail_vs_ptr_error(self): result = self.run_command(['vs', 'detail', '100']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output.get('ptr', None), None) - + self.assertEqual(output.get('ptr', None), None) def test_create_options(self): result = self.run_command(['vs', 'create-options']) @@ -683,30 +683,49 @@ def test_usage_metric_data_empty(self): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_bandwidth_vs(self): + if sys.version_info < (3, 6): + self.skipTest("Test requires python 3.6+") + result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01']) self.assert_no_fail(result) + + date = '2019-05-20 23:00' + # number of characters from the end of output to break so json can parse properly + pivot = 157 + # only pyhon 3.7 supports the timezone format slapi uses + if sys.version_info < (3, 7): + date = '2019-05-20T23:00:00-06:00' + pivot = 166 # Since this is 2 tables, it gets returned as invalid json like "[{}][{}]"" instead of "[[{}],[{}]]" # so we just do some hacky string substitution to pull out the respective arrays that can be jsonifyied - output_summary = json.loads(result.output[0:-157]) - output_list = json.loads(result.output[-158:]) + + output_summary = json.loads(result.output[0:-pivot]) + output_list = json.loads(result.output[-pivot:]) self.assertEqual(output_summary[0]['Average MBps'], 0.3841) - self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) - self.assertEqual(output_list[0]['Date'], '2019-05-20 23:00') + self.assertEqual(output_list[0]['Date'], date) self.assertEqual(output_list[0]['Pub In'], 1.3503) def test_bandwidth_vs_quite(self): result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01', '-q']) self.assert_no_fail(result) + + date = '2019-05-20 23:00' + + # only pyhon 3.7 supports the timezone format slapi uses + if sys.version_info < (3, 7): + date = '2019-05-20T23:00:00-06:00' + output_summary = json.loads(result.output) self.assertEqual(output_summary[0]['Average MBps'], 0.3841) - self.assertEqual(output_summary[1]['Max Date'], '2019-05-20 23:00') + self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) - diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 895146fb2..461094be6 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -327,7 +327,7 @@ def test_cancel_running_transaction(self): 'activeTransaction': {'id': 4567}} self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, - 12345) + 12345) def test_change_port_speed_public(self): self.hardware.change_port_speed(2, True, 100) @@ -426,8 +426,10 @@ def test_get_tracking_id(self): def test_get_bandwidth_data(self): result = self.hardware.get_bandwidth_data(1234, '2019-01-01', '2019-02-01', 'public', 1000) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getBandwidthData', args=('2019-01-01', '2019-02-01', - 'public', 1000), identifier=1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getBandwidthData', + args=('2019-01-01', '2019-02-01', 'public', 1000), + identifier=1000) self.assertEqual(result[0]['type'], 'cpu0') def test_get_bandwidth_allocation(self): @@ -447,9 +449,9 @@ def test_get_extra_price_id_no_items(self): def test_get_extra_price_mismatched(self): items = [ - {'keyName': 'TEST', 'prices':[{'id':1, 'locationGroupId': None, 'recurringFee':99}]}, - {'keyName': 'TEST', 'prices':[{'id':2, 'locationGroupId': 55, 'hourlyRecurringFee':99}]}, - {'keyName': 'TEST', 'prices':[{'id':3, 'locationGroupId': None, 'hourlyRecurringFee':99}]}, + {'keyName': 'TEST', 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}]}, + {'keyName': 'TEST', 'prices': [{'id': 2, 'locationGroupId': 55, 'hourlyRecurringFee': 99}]}, + {'keyName': 'TEST', 'prices': [{'id': 3, 'locationGroupId': None, 'hourlyRecurringFee': 99}]}, ] location = { 'location': { @@ -466,18 +468,18 @@ def test_get_extra_price_mismatched(self): def test_get_bandwidth_price_mismatched(self): items = [ - {'itemCategory': {'categoryCode':'bandwidth'}, - 'capacity': 100, - 'prices':[{'id':1, 'locationGroupId': None, 'hourlyRecurringFee':99}] - }, - {'itemCategory': {'categoryCode':'bandwidth'}, - 'capacity': 100, - 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'bandwidth'}, - 'capacity': 100, - 'prices':[{'id':3, 'locationGroupId': None, 'recurringFee':99}] - }, + {'itemCategory': {'categoryCode': 'bandwidth'}, + 'capacity': 100, + 'prices': [{'id': 1, 'locationGroupId': None, 'hourlyRecurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'bandwidth'}, + 'capacity': 100, + 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'bandwidth'}, + 'capacity': 100, + 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] + }, ] location = { 'location': { @@ -494,14 +496,14 @@ def test_get_bandwidth_price_mismatched(self): def test_get_os_price_mismatched(self): items = [ - {'itemCategory': {'categoryCode':'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, - 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, - 'prices':[{'id':3, 'locationGroupId': None, 'recurringFee':99}] - }, + {'itemCategory': {'categoryCode': 'os'}, + 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'os'}, + 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] + }, ] location = { 'location': { @@ -514,7 +516,7 @@ def test_get_os_price_mismatched(self): } } result = managers.hardware._get_os_price_id(items, 'TEST_OS', location) - self.assertEqual(3, result) + self.assertEqual(3, result) def test_get_default_price_id_item_not_first(self): items = [{ @@ -557,31 +559,31 @@ def test_get_port_speed_price_id_no_items(self): def test_get_port_speed_price_id_mismatch(self): items = [ - {'itemCategory': {'categoryCode':'port_speed'}, - 'capacity':101, - 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices':[{'id':1, 'locationGroupId': None, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'port_speed'}, - 'capacity':100, - 'attributes':[{'attributeTypeKeyName': 'IS_NOT_PRIVATE_NETWORK_ONLY'}], - 'prices':[{'id':2, 'locationGroupId': 55, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'port_speed'}, - 'capacity':100, - 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}, {'attributeTypeKeyName': 'NON_LACP'}], - 'prices':[{'id':3, 'locationGroupId': 55, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'port_speed'}, - 'capacity':100, - 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices':[{'id':4, 'locationGroupId': 12, 'recurringFee':99}] - }, - {'itemCategory': {'categoryCode':'port_speed'}, - 'capacity':100, - 'attributes':[{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices':[{'id':5, 'locationGroupId': None, 'recurringFee':99}] - }, + {'itemCategory': {'categoryCode': 'port_speed'}, + 'capacity': 101, + 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'port_speed'}, + 'capacity': 100, + 'attributes': [{'attributeTypeKeyName': 'IS_NOT_PRIVATE_NETWORK_ONLY'}], + 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'port_speed'}, + 'capacity': 100, + 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}, {'attributeTypeKeyName': 'NON_LACP'}], + 'prices': [{'id': 3, 'locationGroupId': 55, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'port_speed'}, + 'capacity': 100, + 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices': [{'id': 4, 'locationGroupId': 12, 'recurringFee': 99}] + }, + {'itemCategory': {'categoryCode': 'port_speed'}, + 'capacity': 100, + 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], + 'prices': [{'id': 5, 'locationGroupId': None, 'recurringFee': 99}] + }, ] location = { 'location': { @@ -594,10 +596,10 @@ def test_get_port_speed_price_id_mismatch(self): } } result = managers.hardware._get_port_speed_price_id(items, 100, True, location) - self.assertEqual(5, result) + self.assertEqual(5, result) def test_matches_location(self): - price = {'id':1, 'locationGroupId': 51, 'recurringFee':99} + price = {'id': 1, 'locationGroupId': 51, 'recurringFee': 99} location = { 'location': { 'location': { @@ -609,5 +611,4 @@ def test_matches_location(self): } } result = managers.hardware._matches_location(price, location) - self.assertTrue(result) - + self.assertTrue(result) diff --git a/tests/managers/vs/vs_order_tests.py b/tests/managers/vs/vs_order_tests.py index 3a9b273e4..7b54f5450 100644 --- a/tests/managers/vs/vs_order_tests.py +++ b/tests/managers/vs/vs_order_tests.py @@ -140,14 +140,14 @@ def test__get_price_id_for_upgrade_find_private_price(self): option='cpus', value='4', public=False) - self.assertEqual(1007, price_id) + self.assertEqual(1007, price_id) def test_upgrade_mem_and_preset_exception(self): self.assertRaises( ValueError, self.vs.upgrade, - 1234, - memory=10, + 1234, + memory=10, preset="M1_64X512X100" ) @@ -155,8 +155,8 @@ def test_upgrade_cpu_and_preset_exception(self): self.assertRaises( ValueError, self.vs.upgrade, - 1234, - cpus=10, + 1234, + cpus=10, preset="M1_64X512X100" ) @@ -221,23 +221,23 @@ def test_get_price_id_empty(self): upgrade_prices = [ {'categories': None, 'item': None}, {'categories': [{'categoryCode': 'ram'}], 'item': None}, - {'categories': None, 'item': {'capacity':1}}, + {'categories': None, 'item': {'capacity': 1}}, ] - result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) - self.assertEqual(None,result) + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(None, result) def test_get_price_id_memory_capacity(self): upgrade_prices = [ - {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':1},'id':99} + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 99} ] - result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) - self.assertEqual(99,result) + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(99, result) def test_get_price_id_mismatch_capacity(self): upgrade_prices = [ - {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity':1},'id':90}, - {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':2},'id':91}, - {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity':1},'id':92}, + {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity': 1}, 'id': 90}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 2}, 'id': 91}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 92}, ] - result = self.vs._get_price_id_for_upgrade_option(upgrade_prices,'memory',1) - self.assertEqual(92,result) + result = self.vs._get_price_id_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(92, result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index e892028c1..2192e642b 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -484,8 +484,8 @@ def test_generate_sec_group(self): hostname='test', domain='test.com', os_code="OS", - public_security_groups=[1,2,3], - private_security_groups=[4,5,6] + public_security_groups=[1, 2, 3], + private_security_groups=[4, 5, 6] ) pub_sec_binding = data['primaryNetworkComponent']['securityGroupBindings'] @@ -865,7 +865,8 @@ def test_usage_vs_cpu(self): args = ('2019-3-4', '2019-4-2', [{"keyName": "CPU0", "summaryType": "max"}], 300) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getSummaryData', args=args, identifier=1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', args=args, identifier=1000) def test_usage_vs_memory(self): result = self.vs.get_summary_data_usage('100', @@ -888,8 +889,10 @@ def test_get_tracking_id(self): def test_get_bandwidth_data(self): result = self.vs.get_bandwidth_data(1234, '2019-01-01', '2019-02-01', 'public', 1000) - self.assert_called_with('SoftLayer_Metric_Tracking_Object', 'getBandwidthData', args=('2019-01-01', '2019-02-01', - 'public', 1000), identifier=1000) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getBandwidthData', + args=('2019-01-01', '2019-02-01', 'public', 1000), + identifier=1000) self.assertEqual(result[0]['type'], 'cpu0') def test_get_bandwidth_allocation(self): @@ -898,4 +901,3 @@ def test_get_bandwidth_allocation(self): self.assert_called_with('SoftLayer_Virtual_Guest', 'getBillingCycleBandwidthUsage', identifier=1234) self.assertEqual(result['allotment']['amount'], '250') self.assertEqual(result['useage'][0]['amountIn'], '.448') - From c16c7d0557388752a7abcba94f18c3e5078e33e5 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 May 2019 18:13:06 -0500 Subject: [PATCH 0099/1582] more style fixes... --- tests/CLI/modules/vs/vs_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index cbb8ef209..16016d450 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -683,7 +683,6 @@ def test_usage_metric_data_empty(self): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - def test_bandwidth_vs(self): if sys.version_info < (3, 6): self.skipTest("Test requires python 3.6+") @@ -691,7 +690,6 @@ def test_bandwidth_vs(self): result = self.run_command(['vs', 'bandwidth', '100', '--start_date=2019-01-01', '--end_date=2019-02-01']) self.assert_no_fail(result) - date = '2019-05-20 23:00' # number of characters from the end of output to break so json can parse properly pivot = 157 From 540f2b014f820edd9656603eb9bd1d9b45db3fc1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 May 2019 18:14:03 -0500 Subject: [PATCH 0100/1582] removing py27 from testing support --- .travis.yml | 8 +++----- tox.ini | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 735f4f693..fd47c343e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,15 @@ language: python sudo: false matrix: include: - - python: "2.7" - env: TOX_ENV=py27 - python: "3.5" env: TOX_ENV=py35 - python: "3.6" env: TOX_ENV=py36 - - python: "pypy2.7-5.8.0" + - python: "pypy3.7" env: TOX_ENV=pypy - - python: "2.7" + - python: "3.7" env: TOX_ENV=analysis - - python: "2.7" + - python: "3.7" env: TOX_ENV=coverage install: - pip install tox diff --git a/tox.ini b/tox.ini index e5c7c2f66..ff08bac17 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py35,py36,py37,pypy,analysis,coverage +envlist = py35,py36,py37,pypy,analysis,coverage [flake8] From d4e3866a8cbafc19ab8be74b3d4f42b634fb5f94 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 May 2019 19:26:54 -0400 Subject: [PATCH 0101/1582] Added create subnet static ipv6 test --- tests/CLI/modules/subnet_tests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index f55846995..1971aa420 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -114,3 +114,23 @@ def test_create_subnet_static(self, confirm_mock): ] self.assertEqual(output, json.loads(result.output)) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_subnet_static_ipv6(self, confirm_mock): + confirm_mock.return_value = True + + item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + item_mock.return_value = SoftLayer_Product_Package.getItems + + place_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + place_mock.return_value = SoftLayer_Product_Order.verifyOrder + + result = self.run_command(['subnet', 'create', '--v6', 'static', '64', '12346', '--test']) + self.assert_no_fail(result) + + output = [ + {'Item': 'this is a thing', 'cost': '2.00'}, + {'Item': 'Total monthly cost', 'cost': '2.00'} + ] + + self.assertEqual(output, json.loads(result.output)) From 3dcea7eac356c27e1c2f8f0477188ee2010790c1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 29 May 2019 15:50:11 -0500 Subject: [PATCH 0102/1582] added docs for new functions --- docs/cli/hardware.rst | 4 ++ docs/cli/vs.rst | 104 +++++++++++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 31 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f6bb7e488..3e7eeaf4c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -4,6 +4,10 @@ Interacting with Hardware ============================== +.. click:: SoftLayer.CLI.hardware.bandwidth:cli + :prog: hw bandwidth + :show-nested: + .. click:: SoftLayer.CLI.hardware.cancel_reasons:cli :prog: hw cancel-reasons :show-nested: diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 2276bd7e9..f855238d5 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -170,39 +170,81 @@ username is 'root' and password is 'ABCDEFGH'. :..............:...........................: -There are many other commands to help manage virtual servers. To see them all, -use `slcli help vs`. -:: +.. click:: SoftLayer.CLI.virt.bandwidth:cli + :prog: vs bandwidth + :show-nested: + +If no timezone is specified, IMS local time (CST) will be assumed, which might not match your user's selected timezone. + + +.. click:: SoftLayer.CLI.virt.cancel:cli + :prog: vs cancel + :show-nested: + +.. click:: SoftLayer.CLI.virt.capture:cli + :prog: vs capture + :show-nested: + +.. click:: SoftLayer.CLI.virt.create:cli + :prog: vs create + :show-nested: + +.. click:: SoftLayer.CLI.virt.create_options:cli + :prog: vs create-options + :show-nested: + +.. click:: SoftLayer.CLI.virt.dns:cli + :prog: vs dns-sync + :show-nested: + +.. click:: SoftLayer.CLI.virt.edit:cli + :prog: vs edit + :show-nested: + +.. click:: SoftLayer.CLI.virt.list:cli + :prog: vs list + :show-nested: + +.. click:: SoftLayer.CLI.virt.power:pause + :prog: vs pause + :show-nested: + + +.. click:: SoftLayer.CLI.virt.power:power_on + :prog: vs power-on + :show-nested: + + +.. click:: SoftLayer.CLI.virt.power:power_off + :prog: vs power-off + :show-nested: + +.. click:: SoftLayer.CLI.virt.power:resume + :prog: vs resume + :show-nested: + +.. click:: SoftLayer.CLI.virt.power:rescue + :prog: vs rescue + :show-nested: + +.. click:: SoftLayer.CLI.virt.power:reboot + :prog: vs reboot + :show-nested: + +.. click:: SoftLayer.CLI.virt.ready:cli + :prog: vs ready + :show-nested: + +.. click:: SoftLayer.CLI.virt.upgrade:cli + :prog: vs upgrade + :show-nested: + +.. click:: SoftLayer.CLI.virt.usage:cli + :prog: vs usage + :show-nested: + - $ slcli vs - Usage: slcli vs [OPTIONS] COMMAND [ARGS]... - - Virtual Servers. - - Options: - --help Show this message and exit. - - Commands: - cancel Cancel virtual servers. - capture Capture SoftLayer image. - create Order/create virtual servers. - create-options Virtual server order options. - credentials List virtual server credentials. - detail Get details for a virtual server. - dns-sync Sync DNS records. - edit Edit a virtual server's details. - list List virtual servers. - network Manage network settings. - pause Pauses an active virtual server. - power_off Power off an active virtual server. - power_on Power on a virtual server. - ready Check if a virtual server is ready. - reboot Reboot an active virtual server. - reload Reload operating system on a virtual server. - rescue Reboot into a rescue image. - resume Resumes a paused virtual server. - upgrade Upgrade a virtual server. Reserved Capacity From a279a8ab53e120ae11c62b5d6191e2a68ab8b850 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 30 May 2019 14:33:50 -0500 Subject: [PATCH 0103/1582] updating travis environments --- .travis.yml | 8 +++-- docs/cli.rst | 71 ++++++++++++++++++++++++++++++++++++++++++++ docs/cli/reports.rst | 17 +++++++++++ 3 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 docs/cli/reports.rst diff --git a/.travis.yml b/.travis.yml index fd47c343e..a023c9082 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,13 @@ matrix: env: TOX_ENV=py35 - python: "3.6" env: TOX_ENV=py36 - - python: "pypy3.7" - env: TOX_ENV=pypy - python: "3.7" + env: TOX_ENV=py37 + - python: "pypy3.6" + env: TOX_ENV=pypy + - python: "3.6" env: TOX_ENV=analysis - - python: "3.7" + - python: "3.6" env: TOX_ENV=coverage install: - pip install tox diff --git a/docs/cli.rst b/docs/cli.rst index ebd62741e..307592fbd 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -176,3 +176,74 @@ Most commands will take in additional options/arguments. To see all available ac separated tags --help Show this message and exit. + + +Debugging +========= +To see exactly what API call is being made by the SLCLI, you can use the verbose option. + +A single `-v` will show a simple version of the API call, along with some statistics + +:: + + slcli -v vs detail 74397127 + Calling: SoftLayer_Virtual_Guest::getObject(id=74397127, mask='id,globalIdentifier,fullyQualifiedDomainName,hostname,domain', filter='None', args=(), limit=None, offset=None)) + Calling: SoftLayer_Virtual_Guest::getReverseDomainRecords(id=77460683, mask='', filter='None', args=(), limit=None, offset=None)) + :..................:..............................................................: + : name : value : + :..................:..............................................................: + : execution_time : 2.020334s : + : api_calls : SoftLayer_Virtual_Guest::getObject (1.515583s) : + : : SoftLayer_Virtual_Guest::getReverseDomainRecords (0.494480s) : + : version : softlayer-python/v5.7.2 : + : python_version : 3.7.3 (default, Mar 27 2019, 09:23:15) : + : : [Clang 10.0.1 (clang-1001.0.46.3)] : + : library_location : /Users/chris/Code/py3/lib/python3.7/site-packages/SoftLayer : + :..................:..............................................................: + + +Using `-vv` will print out some API call details in the summary as well. + +:: + + slcli -vv account summary + Calling: SoftLayer_Account::getObject(id=None, mask='mask[ nextInvoiceTotalAmount, pendingInvoice[invoiceTotalAmount], blockDeviceTemplateGroupCount, dedicatedHostCount, domainCount, hardwareCount, networkStorageCount, openTicketCount, networkVlanCount, subnetCount, userCount, virtualGuestCount ]', filter='None', args=(), limit=None, offset=None)) + :..................:.............................................................: + : name : value : + :..................:.............................................................: + : execution_time : 0.921271s : + : api_calls : SoftLayer_Account::getObject (0.911208s) : + : version : softlayer-python/v5.7.2 : + : python_version : 3.7.3 (default, Mar 27 2019, 09:23:15) : + : : [Clang 10.0.1 (clang-1001.0.46.3)] : + : library_location : /Users/chris/Code/py3/lib/python3.7/site-packages/SoftLayer : + :..................:.............................................................: + :........:.................................................: + : : SoftLayer_Account::getObject : + :........:.................................................: + : id : None : + : mask : mask[ : + : : nextInvoiceTotalAmount, : + : : pendingInvoice[invoiceTotalAmount], : + : : blockDeviceTemplateGroupCount, : + : : dedicatedHostCount, : + : : domainCount, : + : : hardwareCount, : + : : networkStorageCount, : + : : openTicketCount, : + : : networkVlanCount, : + : : subnetCount, : + : : userCount, : + : : virtualGuestCount : + : : ] : + : filter : None : + : limit : None : + : offset : None : + :........:.................................................: + +Using `-vvv` will print out the exact API that can be used without the softlayer-python framework, A simple python code snippet for XML-RPC, a curl call for REST API calls. This is dependant on the endpoint you are using in the config file. + +:: + + slcli -vvv account summary + curl -u $SL_USER:$SL_APIKEY -X GET -H "Accept: */*" -H "Accept-Encoding: gzip, deflate, compress" 'https://api.softlayer.com/rest/v3.1/SoftLayer_Account/getObject.json?objectMask=mask%5B%0A++++++++++++nextInvoiceTotalAmount%2C%0A++++++++++++pendingInvoice%5BinvoiceTotalAmount%5D%2C%0A++++++++++++blockDeviceTemplateGroupCount%2C%0A++++++++++++dedicatedHostCount%2C%0A++++++++++++domainCount%2C%0A++++++++++++hardwareCount%2C%0A++++++++++++networkStorageCount%2C%0A++++++++++++openTicketCount%2C%0A++++++++++++networkVlanCount%2C%0A++++++++++++subnetCount%2C%0A++++++++++++userCount%2C%0A++++++++++++virtualGuestCount%0A++++++++++++%5D' diff --git a/docs/cli/reports.rst b/docs/cli/reports.rst new file mode 100644 index 000000000..f62de5882 --- /dev/null +++ b/docs/cli/reports.rst @@ -0,0 +1,17 @@ +.. _cli_reports: + +Reports +======= + +There are a few report type commands in the SLCLI. + +.. click:: SoftLayer.CLI.summary:cli + :prog: summary + :show-nested: + +A list of datacenters, and how many servers, VSI, vlans, subnets and public_ips are in each. + + +.. click:: SoftLayer.CLI.report.bandwidth:cli + :prog: report bandwidth + :show-nested: \ No newline at end of file From 920d5f041ba515dc6d037ec5b7c997db7de478b2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 30 May 2019 14:40:15 -0500 Subject: [PATCH 0104/1582] another travisci update --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a023c9082..d69b42d68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +# https://docs.travis-ci.com/user/languages/python/#python-37-and-higher +dist: xenial language: python sudo: false matrix: @@ -8,7 +10,7 @@ matrix: env: TOX_ENV=py36 - python: "3.7" env: TOX_ENV=py37 - - python: "pypy3.6" + - python: "pypy3.5" env: TOX_ENV=pypy - python: "3.6" env: TOX_ENV=analysis From 9ef4e7f279b75b4f68ad9b2092de785026b09a67 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 31 May 2019 17:10:04 -0400 Subject: [PATCH 0105/1582] removed commented code --- SoftLayer/managers/network.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index cdbf86ba1..dbfb9c3f6 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -148,15 +148,9 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, # item description. price_id = None quantity_str = str(quantity) - # package_items = package.getItems(id=0, mask='itemCategory') package_items = package.getItems(id=0) for item in package_items: category_code = utils.lookup(item, 'itemCategory', 'categoryCode') - # if (category_code == category - # and item.get('capacity') == quantity_str - # and (version == 4 or (version == 6 and desc in item['description']))): - # price_id = item['prices'][0]['id'] - # break if all([category_code == category, item.get('capacity') == quantity_str, version == 4 or (version == 6 and From b3f52bc6ec5a9de4a8e21b1fd8fbf1e92a871595 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 31 May 2019 19:11:10 -0400 Subject: [PATCH 0106/1582] subnet create help message updated --- SoftLayer/CLI/subnet/create.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/subnet/create.py b/SoftLayer/CLI/subnet/create.py index 7d5d6d912..5918c5859 100644 --- a/SoftLayer/CLI/subnet/create.py +++ b/SoftLayer/CLI/subnet/create.py @@ -22,21 +22,21 @@ def cli(env, network, quantity, endpoint_id, ipv6, test): """Add a new subnet to your account. Valid quantities vary by type. \b - Type - Valid Quantities (IPv4) + IPv4 static - 1, 2, 4, 8, 16, 32, 64, 128, 256 public - 4, 8, 16, 32, 64, 128, 256 private - 4, 8, 16, 32, 64, 128, 256 \b - Type - Valid Quantities (IPv6) + IPv6 static - 64 public - 64 \b - Type - endpoint-id - static - IP address identifier. - public - VLAN identifier - private - VLAN identifier + endpoint-id + static - Network_Subnet_IpAddress identifier. + public - Network_Vlan identifier + private - Network_Vlan identifier """ mgr = SoftLayer.NetworkManager(env.client) From 1e6855655c4e200f71b39c5b28bc7be48051f031 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 5 Jun 2019 14:22:54 -0500 Subject: [PATCH 0107/1582] updated help message for bandwidth --- SoftLayer/CLI/hardware/bandwidth.py | 3 +++ SoftLayer/CLI/virt/bandwidth.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 1984d8658..886e94052 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -26,6 +26,9 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. + Due to some rounding and date alignment details, results here might be slightly different than + results in the control portal. + Example:: slcli hw bandwidth 1234 -s 2019-05-01T00:01 -e 2019-05-02T00:00:01.00000-12:00 diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 235d7f406..b67d4c7c6 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -27,6 +27,9 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. + Due to some rounding and date alignment details, results here might be slightly different than + results in the control portal. + Example:: slcli hw bandwidth 1234 -s 2019-05-01T00:01 -e 2019-05-02T00:00:01.00000-12:00 From a86f106da258bddbed017e2e97b9656d13c485b3 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 5 Jun 2019 15:31:18 -0500 Subject: [PATCH 0108/1582] fixing trailing whitespace --- SoftLayer/CLI/hardware/bandwidth.py | 4 ++-- SoftLayer/CLI/virt/bandwidth.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index 886e94052..efe821c29 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -26,8 +26,8 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. - Due to some rounding and date alignment details, results here might be slightly different than - results in the control portal. + Due to some rounding and date alignment details, results here might be slightly different than + results in the control portal. Example:: diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index b67d4c7c6..2f29cc7f8 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -27,8 +27,8 @@ def cli(env, identifier, start_date, end_date, summary_period, quite_summary): Using just a date might get you times off by 1 hour, use T00:01 to get just the specific days data Timezones can also be included with the YYYY-MM-DDTHH:mm:ss.00000-HH:mm format. - Due to some rounding and date alignment details, results here might be slightly different than - results in the control portal. + Due to some rounding and date alignment details, results here might be slightly different than + results in the control portal. Example:: From 3ef580e9b4930bf7f04851fe4cc421663a3e0c82 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 10 Jun 2019 11:23:12 -0400 Subject: [PATCH 0109/1582] Feature cdn network. --- SoftLayer/CLI/cdn/detail.py | 34 ++- SoftLayer/CLI/cdn/list.py | 37 +-- SoftLayer/CLI/cdn/origin_add.py | 78 ++++++- SoftLayer/CLI/cdn/origin_list.py | 18 +- SoftLayer/CLI/cdn/origin_remove.py | 12 +- SoftLayer/CLI/cdn/purge.py | 25 +- ...dnMarketplace_Configuration_Cache_Purge.py | 1 + ...rk_CdnMarketplace_Configuration_Mapping.py | 31 +++ ...nMarketplace_Configuration_Mapping_Path.py | 35 +++ ...oftLayer_Network_CdnMarketplace_Metrics.py | 15 ++ SoftLayer/managers/cdn.py | 217 ++++++++++-------- SoftLayer/utils.py | 27 +++ tests/CLI/modules/cdn_tests.py | 79 +++---- tests/managers/cdn_tests.py | 171 +++++--------- 14 files changed, 462 insertions(+), 318 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Metrics.py diff --git a/SoftLayer/CLI/cdn/detail.py b/SoftLayer/CLI/cdn/detail.py index 509db5362..fe067d5b3 100644 --- a/SoftLayer/CLI/cdn/detail.py +++ b/SoftLayer/CLI/cdn/detail.py @@ -9,24 +9,38 @@ @click.command() -@click.argument('account_id') +@click.argument('unique_id') +@click.option('--last_days', + default=30, + help='cdn overview last days less than 90 days, because it is the maximum e.g 7, 15, 30, 60, 89') @environment.pass_env -def cli(env, account_id): +def cli(env, unique_id, last_days): """Detail a CDN Account.""" manager = SoftLayer.CDNManager(env.client) - account = manager.get_account(account_id) + + cdn_mapping = manager.get_cdn(unique_id) + cdn_metrics = manager.get_usage_metrics(unique_id, days=last_days) + + # usage metrics + total_bandwidth = str(cdn_metrics['totals'][0]) + " GB" + total_hits = str(cdn_metrics['totals'][1]) + hit_radio = str(cdn_metrics['totals'][2]) + " %" table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', account['id']]) - table.add_row(['account_name', account['cdnAccountName']]) - table.add_row(['type', account['cdnSolutionName']]) - table.add_row(['status', account['status']['name']]) - table.add_row(['created', account['createDate']]) - table.add_row(['notes', - account.get('cdnAccountNote', formatting.blank())]) + table.add_row(['unique_id', cdn_mapping['uniqueId']]) + table.add_row(['hostname', cdn_mapping['domain']]) + table.add_row(['protocol', cdn_mapping['protocol']]) + table.add_row(['origin', cdn_mapping['originHost']]) + table.add_row(['origin_type', cdn_mapping['originType']]) + table.add_row(['path', cdn_mapping['path']]) + table.add_row(['provider', cdn_mapping['vendorName']]) + table.add_row(['status', cdn_mapping['status']]) + table.add_row(['total_bandwidth', total_bandwidth]) + table.add_row(['total_hits', total_hits]) + table.add_row(['hit_radio', hit_radio]) env.fout(table) diff --git a/SoftLayer/CLI/cdn/list.py b/SoftLayer/CLI/cdn/list.py index 2e1b07785..994a338b3 100644 --- a/SoftLayer/CLI/cdn/list.py +++ b/SoftLayer/CLI/cdn/list.py @@ -11,32 +11,33 @@ @click.command() @click.option('--sortby', help='Column to sort by', - type=click.Choice(['id', - 'datacenter', - 'host', - 'cores', - 'memory', - 'primary_ip', - 'backend_ip'])) + type=click.Choice(['unique_id', + 'domain', + 'origin', + 'vendor', + 'cname', + 'status'])) @environment.pass_env def cli(env, sortby): """List all CDN accounts.""" manager = SoftLayer.CDNManager(env.client) - accounts = manager.list_accounts() + accounts = manager.list_cdn() - table = formatting.Table(['id', - 'account_name', - 'type', - 'created', - 'notes']) + table = formatting.Table(['unique_id', + 'domain', + 'origin', + 'vendor', + 'cname', + 'status']) for account in accounts: table.add_row([ - account['id'], - account['cdnAccountName'], - account['cdnSolutionName'], - account['createDate'], - account.get('cdnAccountNote', formatting.blank()) + account['uniqueId'], + account['domain'], + account['originHost'], + account['vendorName'], + account['cname'], + account['status'] ]) table.sortby = sortby diff --git a/SoftLayer/CLI/cdn/origin_add.py b/SoftLayer/CLI/cdn/origin_add.py index 51d789da9..413b9c446 100644 --- a/SoftLayer/CLI/cdn/origin_add.py +++ b/SoftLayer/CLI/cdn/origin_add.py @@ -5,22 +5,78 @@ import SoftLayer from SoftLayer.CLI import environment - -# pylint: disable=redefined-builtin +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting @click.command() -@click.argument('account_id') -@click.argument('content_url') -@click.option('--type', - help='The media type for this mapping (http, flash, wm, ...)', +@click.argument('unique_id') +@click.argument('origin') +@click.argument('path') +@click.option('--origin-type', '-t', + type=click.Choice(['server', 'storage']), + help='The origin type.', + default='server', + show_default=True) +@click.option('--header', '-H', + type=click.STRING, + help='The host header to communicate with the origin.') +@click.option('--bucket-name', '-b', + type=click.STRING, + help="The name of the available resource [required if --origin-type=storage]") +@click.option('--port', '-p', + type=click.INT, + help="The http port number.", + default=80, + show_default=True) +@click.option('--protocol', '-P', + type=click.STRING, + help="The protocol used by the origin.", default='http', show_default=True) -@click.option('--cname', - help='An optional CNAME to attach to the mapping') +@click.option('--optimize-for', '-o', + type=click.Choice(['web', 'video', 'file']), + help="Performance configuration", + default='web', + show_default=True) +@click.option('--extensions', '-e', + type=click.STRING, + help="File extensions that can be stored in the CDN, example: 'jpg, png, pdf'") +@click.option('--cache-query', '-c', + type=click.STRING, + help="Cache query rules with the following formats:\n" + "'ignore-all', 'include: ', 'ignore: '", + default="include-all", + show_default=True) @environment.pass_env -def cli(env, account_id, content_url, type, cname): - """Create an origin pull mapping.""" +def cli(env, unique_id, origin, path, origin_type, header, + bucket_name, port, protocol, optimize_for, extensions, cache_query): + """Create an origin path for an existing CDN mapping.""" manager = SoftLayer.CDNManager(env.client) - manager.add_origin(account_id, type, content_url, cname) + + if origin_type == 'storage' and not bucket_name: + raise exceptions.ArgumentError('[-b | --bucket-name] is required when [-t | --origin-type] is "storage"') + + result = manager.add_origin(unique_id, origin, path, origin_type=origin_type, + header=header, port=port, protocol=protocol, + bucket_name=bucket_name, file_extensions=extensions, + optimize_for=optimize_for, cache_query=cache_query) + + table = formatting.Table(['Item', 'Value']) + table.align['Item'] = 'r' + table.align['Value'] = 'r' + + table.add_row(['CDN Unique ID', result['mappingUniqueId']]) + + if origin_type == 'storage': + table.add_row(['Bucket Name', result['bucketName']]) + + table.add_row(['Origin', result['origin']]) + table.add_row(['Origin Type', result['originType']]) + table.add_row(['Path', result['path']]) + table.add_row(['Port', result['httpPort']]) + table.add_row(['Configuration', result['performanceConfiguration']]) + table.add_row(['Status', result['status']]) + + env.fout(table) diff --git a/SoftLayer/CLI/cdn/origin_list.py b/SoftLayer/CLI/cdn/origin_list.py index 1867a9cdd..208c26f61 100644 --- a/SoftLayer/CLI/cdn/origin_list.py +++ b/SoftLayer/CLI/cdn/origin_list.py @@ -9,20 +9,20 @@ @click.command() -@click.argument('account_id') +@click.argument('unique_id') @environment.pass_env -def cli(env, account_id): - """List origin pull mappings.""" +def cli(env, unique_id): + """List origin path for an existing CDN mapping.""" manager = SoftLayer.CDNManager(env.client) - origins = manager.get_origins(account_id) + origins = manager.get_origins(unique_id) - table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) + table = formatting.Table(['Path', 'Origin', 'HTTP Port', 'Status']) for origin in origins: - table.add_row([origin['id'], - origin['mediaType'], - origin.get('cname', formatting.blank()), - origin['originUrl']]) + table.add_row([origin['path'], + origin['origin'], + origin['httpPort'], + origin['status']]) env.fout(table) diff --git a/SoftLayer/CLI/cdn/origin_remove.py b/SoftLayer/CLI/cdn/origin_remove.py index 2b8855ede..4e4172387 100644 --- a/SoftLayer/CLI/cdn/origin_remove.py +++ b/SoftLayer/CLI/cdn/origin_remove.py @@ -8,11 +8,13 @@ @click.command() -@click.argument('account_id') -@click.argument('origin_id') +@click.argument('unique_id') +@click.argument('origin_path') @environment.pass_env -def cli(env, account_id, origin_id): - """Remove an origin pull mapping.""" +def cli(env, unique_id, origin_path): + """Removes an origin path for an existing CDN mapping.""" manager = SoftLayer.CDNManager(env.client) - manager.remove_origin(account_id, origin_id) + manager.remove_origin(unique_id, origin_path) + + click.secho("Origin with path %s has been deleted" % origin_path, fg='green') diff --git a/SoftLayer/CLI/cdn/purge.py b/SoftLayer/CLI/cdn/purge.py index bcf055064..d029aba56 100644 --- a/SoftLayer/CLI/cdn/purge.py +++ b/SoftLayer/CLI/cdn/purge.py @@ -9,26 +9,27 @@ @click.command() -@click.argument('account_id') -@click.argument('content_url', nargs=-1) +@click.argument('unique_id') +@click.argument('path') @environment.pass_env -def cli(env, account_id, content_url): - """Purge cached files from all edge nodes. +def cli(env, unique_id, path): + """Creates a purge record and also initiates the purge call. - Examples: - slcli cdn purge 97794 http://example.com/cdn/file.txt - slcli cdn purge 97794 http://example.com/cdn/file.txt https://dal01.example.softlayer.net/image.png + Example: + slcli cdn purge 9779455 /article/file.txt """ manager = SoftLayer.CDNManager(env.client) - content_list = manager.purge_content(account_id, content_url) + result = manager.purge_content(unique_id, path) - table = formatting.Table(['url', 'status']) + table = formatting.Table(['Date', 'Path', 'Saved', 'Status']) - for content in content_list: + for data in result: table.add_row([ - content['url'], - content['statusCode'] + data['date'], + data['path'], + data['saved'], + data['status'] ]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge.py new file mode 100644 index 000000000..cd0d2810a --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge.py @@ -0,0 +1 @@ +createPurge = [] diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py new file mode 100644 index 000000000..51950b919 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py @@ -0,0 +1,31 @@ +listDomainMappings = [ + { + "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "domain": "test.example.com", + "header": "test.example.com", + "httpPort": 80, + "originHost": "1.1.1.1", + "originType": "HOST_SERVER", + "path": "/", + "protocol": "HTTP", + "status": "CNAME_CONFIGURATION", + "uniqueId": "9934111111111", + "vendorName": "akamai" + } +] + +listDomainMappingByUniqueId = [ + { + "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "domain": "test.example.com", + "header": "test.example.com", + "httpPort": 80, + "originHost": "1.1.1.1", + "originType": "HOST_SERVER", + "path": "/", + "protocol": "HTTP", + "status": "CNAME_CONFIGURATION", + "uniqueId": "9934111111111", + "vendorName": "akamai" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py new file mode 100644 index 000000000..0bfa71375 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py @@ -0,0 +1,35 @@ +listOriginPath = [ + { + "header": "test.example.com", + "httpPort": 80, + "mappingUniqueId": "993419389425697", + "origin": "10.10.10.1", + "originType": "HOST_SERVER", + "path": "/example", + "status": "RUNNING" + }, + { + "header": "test.example.com", + "httpPort": 80, + "mappingUniqueId": "993419389425697", + "origin": "10.10.10.1", + "originType": "HOST_SERVER", + "path": "/example1", + "status": "RUNNING" + } +] + +createOriginPath = [ + { + "header": "test.example.com", + "httpPort": 80, + "mappingUniqueId": "993419389425697", + "origin": "10.10.10.1", + "originType": "HOST_SERVER", + "path": "/example", + "status": "RUNNING", + "performanceConfiguration": "General web delivery" + } +] + +deleteOriginPath = "Origin with path /example/videos/* has been deleted" diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Metrics.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Metrics.py new file mode 100644 index 000000000..6b6aab5b1 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Metrics.py @@ -0,0 +1,15 @@ +getMappingUsageMetrics = [ + { + "names": [ + "TotalBandwidth", + "TotalHits", + "HitRatio" + ], + "totals": [ + "0.0", + "0", + "0.0" + ], + "type": "TOTALS" + } +] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 19a88efb7..f39b9afb6 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -5,140 +5,159 @@ :license: MIT, see LICENSE for more details. """ -import six +from SoftLayer import exceptions from SoftLayer import utils -MAX_URLS_PER_LOAD = 5 -MAX_URLS_PER_PURGE = 5 - - class CDNManager(utils.IdentifierMixin, object): - """Manage CDN accounts and content. + """Manage Content Delivery Networks in the account. See product information here: - http://www.softlayer.com/content-delivery-network + https://www.ibm.com/cloud/cdn + https://cloud.ibm.com/docs/infrastructure/CDN?topic=CDN-about-content-delivery-networks-cdn- :param SoftLayer.API.BaseClient client: the client instance """ def __init__(self, client): self.client = client - self.account = self.client['Network_ContentDelivery_Account'] - - def list_accounts(self): - """Lists CDN accounts for the active user.""" - - account = self.client['Account'] - mask = 'cdnAccounts[%s]' % ', '.join(['id', - 'createDate', - 'cdnAccountName', - 'cdnSolutionName', - 'cdnAccountNote', - 'status']) - return account.getObject(mask=mask).get('cdnAccounts', []) - - def get_account(self, account_id, **kwargs): - """Retrieves a CDN account with the specified account ID. - - :param account_id int: the numeric ID associated with the CDN account. - :param dict \\*\\*kwargs: additional arguments to include in the object - mask. - """ + self.cdn_configuration = self.client['Network_CdnMarketplace_Configuration_Mapping'] + self.cdn_path = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path'] + self.cdn_metrics = self.client['Network_CdnMarketplace_Metrics'] + self.cdn_purge = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge'] - if 'mask' not in kwargs: - kwargs['mask'] = 'status' + def list_cdn(self, **kwargs): + """Lists Content Delivery Networks for the active user. - return self.account.getObject(id=account_id, **kwargs) + :param dict \\*\\*kwargs: header-level options (mask, limit, etc.) + :returns: The list of CDN objects in the account + """ - def get_origins(self, account_id, **kwargs): - """Retrieves list of origin pull mappings for a specified CDN account. + return self.cdn_configuration.listDomainMappings(**kwargs) - :param account_id int: the numeric ID associated with the CDN account. - :param dict \\*\\*kwargs: additional arguments to include in the object - mask. - """ + def get_cdn(self, unique_id, **kwargs): + """Retrieves the information about the CDN account object. - return self.account.getOriginPullMappingInformation(id=account_id, - **kwargs) - - def add_origin(self, account_id, media_type, origin_url, cname=None, - secure=False): - """Adds an original pull mapping to an origin-pull. - - :param int account_id: the numeric ID associated with the CDN account. - :param string media_type: the media type/protocol associated with this - origin pull mapping; valid values are HTTP, - FLASH, and WM. - :param string origin_url: the base URL from which content should be - pulled. - :param string cname: an optional CNAME that should be associated with - this origin pull rule; only the hostname should be - included (i.e., no 'http://', directories, etc.). - :param boolean secure: specifies whether this is an SSL origin pull - rule, if SSL is enabled on your account - (defaults to false). + :param str unique_id: The unique ID associated with the CDN. + :param dict \\*\\*kwargs: header-level option (mask) + :returns: The CDN object """ - config = {'mediaType': media_type, - 'originUrl': origin_url, - 'isSecureContent': secure} + cdn_list = self.cdn_configuration.listDomainMappingByUniqueId(unique_id, **kwargs) - if cname: - config['cname'] = cname + # The method listDomainMappingByUniqueId() returns an array but there is only 1 object + return cdn_list[0] - return self.account.createOriginPullMapping(config, id=account_id) + def get_origins(self, unique_id, **kwargs): + """Retrieves list of origin pull mappings for a specified CDN account. + + :param str unique_id: The unique ID associated with the CDN. + :param dict \\*\\*kwargs: header-level options (mask, limit, etc.) + :returns: The list of origin paths in the CDN object. + """ - def remove_origin(self, account_id, origin_id): + return self.cdn_path.listOriginPath(unique_id, **kwargs) + + def add_origin(self, unique_id, origin, path, origin_type="server", header=None, + port=80, protocol='http', bucket_name=None, file_extensions=None, + optimize_for="web", cache_query="include all"): + """Creates an origin path for an existing CDN. + + :param str unique_id: The unique ID associated with the CDN. + :param str path: relative path to the domain provided, e.g. "/articles/video" + :param str origin: ip address or hostname if origin_type=server, API endpoint for + your S3 object storage if origin_type=storage + :param str origin_type: it can be 'server' or 'storage' types. + :param str header: the edge server uses the host header to communicate with the origin. + It defaults to hostname. (optional) + :param int port: the http port number (default: 80) + :param str protocol: the protocol of the origin (default: HTTP) + :param str bucket_name: name of the available resource + :param str file_extensions: file extensions that can be stored in the CDN, e.g. "jpg,png" + :param str optimize_for: performance configuration, available options: web, video, and file + where: + 'web' --> 'General web delivery' + 'video' --> 'Video on demand optimization' + 'file' --> 'Large file optimization' + :param str cache_query: rules with the following formats: 'include-all', 'ignore-all', + 'include: space separated query-names', + 'ignore: space separated query-names'.' + :return: a CDN origin path object + """ + types = {'server': 'HOST_SERVER', 'storage': 'OBJECT_STORAGE'} + performance_config = { + 'web': 'General web delivery', + 'video': 'Video on demand optimization', + 'file': 'Large file optimization' + } + + new_origin = { + 'uniqueId': unique_id, + 'path': path, + 'origin': origin, + 'originType': types.get(origin_type, 'HOST_SERVER'), + 'httpPort': port, + 'protocol': protocol.upper(), + 'performanceConfiguration': performance_config.get(optimize_for, 'General web delivery'), + 'cacheKeyQueryRule': cache_query + } + + if header: + new_origin['header'] = header + + if types.get(origin_type) == 'OBJECT_STORAGE': + if bucket_name: + new_origin['bucketName'] = bucket_name + else: + raise exceptions.SoftLayerError("Bucket name is required when the origin type is OBJECT_STORAGE") + + if file_extensions: + new_origin['fileExtension'] = file_extensions + + origin = self.cdn_path.createOriginPath(new_origin) + + # The method createOriginPath() returns an array but there is only 1 object + return origin[0] + + def remove_origin(self, unique_id, path): """Removes an origin pull mapping with the given origin pull ID. - :param int account_id: the CDN account ID from which the mapping should - be deleted. - :param int origin_id: the origin pull mapping ID to delete. + :param str unique_id: The unique ID associated with the CDN. + :param str path: The origin path to delete. + :returns: A string value """ - return self.account.deleteOriginPullRule(origin_id, id=account_id) + return self.cdn_path.deleteOriginPath(unique_id, path) - def load_content(self, account_id, urls): - """Prefetches one or more URLs to the CDN edge nodes. + def purge_content(self, unique_id, path): + """Purges a URL or path from the CDN. - :param int account_id: the CDN account ID into which content should be - preloaded. - :param urls: a string or a list of strings representing the CDN URLs - that should be pre-loaded. - :returns: true if all load requests were successfully submitted; - otherwise, returns the first error encountered. + :param str unique_id: The unique ID associated with the CDN. + :param str path: A string of url or path that should be purged. + :returns: A Container_Network_CdnMarketplace_Configuration_Cache_Purge array object """ - if isinstance(urls, six.string_types): - urls = [urls] - - for i in range(0, len(urls), MAX_URLS_PER_LOAD): - result = self.account.loadContent(urls[i:i + MAX_URLS_PER_LOAD], - id=account_id) - if not result: - return result + return self.cdn_purge.createPurge(unique_id, path) - return True + def get_usage_metrics(self, unique_id, days=30, frequency="aggregate"): + """Retrieves the cdn usage metrics. - def purge_content(self, account_id, urls): - """Purges one or more URLs from the CDN edge nodes. + It uses the 'days' argument if start_date and end_date are None. - :param int account_id: the CDN account ID from which content should - be purged. - :param urls: a string or a list of strings representing the CDN URLs - that should be purged. - :returns: a list of SoftLayer_Container_Network_ContentDelivery_PurgeService_Response objects - which indicates if the purge for each url was SUCCESS, FAILED or INVALID_URL. + :param int unique_id: The CDN uniqueId from which the usage metrics will be obtained. + :param int days: Last N days, default days is 30. + :param str frequency: It can be day, week, month and aggregate. The default is "aggregate". + :returns: A Container_Network_CdnMarketplace_Metrics object """ - if isinstance(urls, six.string_types): - urls = [urls] + _start = utils.days_to_datetime(days) + _end = utils.days_to_datetime(0) + + _start_date = utils.timestamp(_start) + _end_date = utils.timestamp(_end) - content_list = [] - for i in range(0, len(urls), MAX_URLS_PER_PURGE): - content = self.account.purgeCache(urls[i:i + MAX_URLS_PER_PURGE], id=account_id) - content_list.extend(content) + usage = self.cdn_metrics.getMappingUsageMetrics(unique_id, _start_date, _end_date, frequency) - return content_list + # The method getMappingUsageMetrics() returns an array but there is only 1 object + return usage[0] diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index f4904adf6..ac718593b 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -7,6 +7,7 @@ """ import datetime import re +import time import six @@ -311,3 +312,29 @@ def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H: # The %z option only exists with py3.6+ except ValueError: return sltime + + +def timestamp(date): + """Converts a datetime to timestamp + + :param datetime date: + :returns int: The timestamp of date. + """ + + _timestamp = time.mktime(date.timetuple()) + + return int(_timestamp) + + +def days_to_datetime(days): + """ Returns the datetime value of last N days. + + :param int days: From 0 to N days + :returns int: The datetime of last N days or datetime.now() if days <= 0. + """ + date = datetime.datetime.now() + + if days > 0: + date -= datetime.timedelta(days=days) + + return date diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index b39e8b8eb..c1427f22e 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -4,10 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import testing - import json +from SoftLayer import testing + class CdnTests(testing.TestCase): @@ -16,67 +16,60 @@ def test_list_accounts(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'notes': None, - 'created': '2012-06-25T14:05:28-07:00', - 'type': 'ORIGIN_PULL', - 'id': 1234, - 'account_name': '1234a'}, - {'notes': None, - 'created': '2012-07-24T13:34:25-07:00', - 'type': 'POP_PULL', - 'id': 1234, - 'account_name': '1234a'}]) + [{'cname': 'cdnakauuiet7s6u6.cdnedge.bluemix.net', + 'domain': 'test.example.com', + 'origin': '1.1.1.1', + 'status': 'CNAME_CONFIGURATION', + 'unique_id': '9934111111111', + 'vendor': 'akamai'}] + ) def test_detail_account(self): - result = self.run_command(['cdn', 'detail', '1245']) + result = self.run_command(['cdn', 'detail', '--last_days=30', '1245']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'notes': None, - 'created': '2012-06-25T14:05:28-07:00', - 'type': 'ORIGIN_PULL', - 'status': 'ACTIVE', - 'id': 1234, - 'account_name': '1234a'}) - - def test_load_content(self): - result = self.run_command(['cdn', 'load', '1234', - 'http://example.com']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") + {'hit_radio': '0.0 %', + 'hostname': 'test.example.com', + 'origin': '1.1.1.1', + 'origin_type': 'HOST_SERVER', + 'path': '/', + 'protocol': 'HTTP', + 'provider': 'akamai', + 'status': 'CNAME_CONFIGURATION', + 'total_bandwidth': '0.0 GB', + 'total_hits': '0', + 'unique_id': '9934111111111'} + ) def test_purge_content(self): result = self.run_command(['cdn', 'purge', '1234', - 'http://example.com']) - expected = [{"url": "http://example.com", "status": "SUCCESS"}] + '/article/file.txt']) + self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), expected) def test_list_origins(self): result = self.run_command(['cdn', 'origin-list', '1234']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), [ - {'media_type': 'FLASH', - 'origin_url': 'http://ams01.objectstorage.softlayer.net:80', - 'cname': None, - 'id': '12345'}, - {'media_type': 'FLASH', - 'origin_url': 'http://sng01.objectstorage.softlayer.net:80', - 'cname': None, - 'id': '12345'}]) + self.assertEqual(json.loads(result.output), [{'HTTP Port': 80, + 'Origin': '10.10.10.1', + 'Path': '/example', + 'Status': 'RUNNING'}, + {'HTTP Port': 80, + 'Origin': '10.10.10.1', + 'Path': '/example1', + 'Status': 'RUNNING'}]) def test_add_origin(self): - result = self.run_command(['cdn', 'origin-add', '1234', - 'http://example.com']) + result = self.run_command(['cdn', 'origin-add', '-H=test.example.com', '-p', 80, '-o', 'web', '-c=include-all', + '1234', '10.10.10.1', '/example/videos2']) self.assert_no_fail(result) - self.assertEqual(result.output, "") def test_remove_origin(self): result = self.run_command(['cdn', 'origin-remove', '1234', - 'http://example.com']) + '/example1']) self.assert_no_fail(result) - self.assertEqual(result.output, "") + self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 8de4bd508..988819830 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -4,12 +4,10 @@ :license: MIT, see LICENSE for more details. """ -import math -import mock -from SoftLayer import fixtures -from SoftLayer.managers import cdn from SoftLayer import testing +from SoftLayer import utils +from SoftLayer.managers import cdn class CDNTests(testing.TestCase): @@ -18,123 +16,74 @@ def set_up(self): self.cdn_client = cdn.CDNManager(self.client) def test_list_accounts(self): - accounts = self.cdn_client.list_accounts() - self.assertEqual(accounts, fixtures.SoftLayer_Account.getCdnAccounts) + self.cdn_client.list_cdn() + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'listDomainMappings') - def test_get_account(self): - account = self.cdn_client.get_account(12345) - self.assertEqual( - account, - fixtures.SoftLayer_Network_ContentDelivery_Account.getObject) - - def test_get_origins(self): - origins = self.cdn_client.get_origins(12345) - self.assertEqual( - origins, - fixtures.SoftLayer_Network_ContentDelivery_Account. - getOriginPullMappingInformation) + def test_detail_cdn(self): + self.cdn_client.get_cdn("12345") - def test_add_origin(self): - self.cdn_client.add_origin(12345, - 'http', - 'http://localhost/', - 'self.local', - False) + args = ("12345",) + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'listDomainMappingByUniqueId', + args=args) - args = ({ - 'mediaType': 'http', - 'originUrl': 'http://localhost/', - 'cname': 'self.local', - 'isSecureContent': False - },) - self.assert_called_with('SoftLayer_Network_ContentDelivery_Account', - 'createOriginPullMapping', - args=args, - identifier=12345) + def test_detail_usage_metric(self): + self.cdn_client.get_usage_metrics(12345, days=30, frequency="aggregate") - def test_remove_origin(self): - self.cdn_client.remove_origin(12345, 12345) - self.assert_called_with('SoftLayer_Network_ContentDelivery_Account', - 'deleteOriginPullRule', - args=(12345,), - identifier=12345) - - def test_load_content(self): - urls = ['http://a/img/0x001.png', - 'http://b/img/0x002.png', - 'http://c/img/0x004.png', - 'http://d/img/0x008.png', - 'http://e/img/0x010.png', - 'http://e/img/0x020.png'] - - self.cdn_client.load_content(12345, urls) - calls = self.calls('SoftLayer_Network_ContentDelivery_Account', - 'loadContent') - self.assertEqual(len(calls), - math.ceil(len(urls) / float(cdn.MAX_URLS_PER_LOAD))) - - def test_load_content_single(self): - url = 'http://geocities.com/Area51/Meteor/12345/under_construction.gif' - self.cdn_client.load_content(12345, url) - - self.assert_called_with('SoftLayer_Network_ContentDelivery_Account', - 'loadContent', - args=([url],), - identifier=12345) - - def test_load_content_failure(self): - urls = ['http://z/img/0x004.png', - 'http://y/img/0x002.png', - 'http://x/img/0x001.png'] - - service = self.client['SoftLayer_Network_ContentDelivery_Account'] - service.loadContent.return_value = False - - self.cdn_client.load_content(12345, urls) - calls = self.calls('SoftLayer_Network_ContentDelivery_Account', - 'loadContent') - self.assertEqual(len(calls), - math.ceil(len(urls) / float(cdn.MAX_URLS_PER_LOAD))) + _start = utils.days_to_datetime(30) + _end = utils.days_to_datetime(0) - def test_purge_content(self): - urls = ['http://z/img/0x020.png', - 'http://y/img/0x010.png', - 'http://x/img/0x008.png', - 'http://w/img/0x004.png', - 'http://v/img/0x002.png', - 'http://u/img/0x001.png'] + _start_date = utils.timestamp(_start) + _end_date = utils.timestamp(_end) - self.cdn_client.purge_content(12345, urls) - calls = self.calls('SoftLayer_Network_ContentDelivery_Account', - 'purgeCache') - self.assertEqual(len(calls), - math.ceil(len(urls) / float(cdn.MAX_URLS_PER_PURGE))) + args = (12345, + _start_date, + _end_date, + "aggregate") + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Metrics', + 'getMappingUsageMetrics', + args=args) - def test_purge_content_failure(self): - urls = ['http://', - 'http://y/img/0x002.png', - 'http://x/img/0x001.png'] - - contents = [ - {'url': urls[0], 'statusCode': 'INVALID_URL'}, - {'url': urls[1], 'statusCode': 'FAILED'}, - {'url': urls[2], 'statusCode': 'FAILED'} - ] - - self.cdn_client.account = mock.Mock() - self.cdn_client.account.purgeCache.return_value = contents + def test_get_origins(self): + self.cdn_client.get_origins("12345") + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', + 'listOriginPath') - result = self.cdn_client.purge_content(12345, urls) + def test_add_origin(self): + self.cdn_client.add_origin("12345", "10.10.10.1", "/example/videos", origin_type="server", + header="test.example.com", port=80, protocol='http', optimize_for="web", + cache_query="include all") - self.assertEqual(contents, result) + args = ({ + 'uniqueId': "12345", + 'origin': '10.10.10.1', + 'path': '/example/videos', + 'originType': 'HOST_SERVER', + 'header': 'test.example.com', + 'httpPort': 80, + 'protocol': 'HTTP', + 'performanceConfiguration': 'General web delivery', + 'cacheKeyQueryRule': "include all" + },) + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', + 'createOriginPath', + args=args) - def test_purge_content_single(self): - url = 'http://geocities.com/Area51/Meteor/12345/under_construction.gif' - self.cdn_client.account = mock.Mock() - self.cdn_client.account.purgeCache.return_value = [{'url': url, 'statusCode': 'SUCCESS'}] + def test_remove_origin(self): + self.cdn_client.remove_origin("12345", "/example1") - expected = [{'url': url, 'statusCode': 'SUCCESS'}] + args = ("12345", + "/example1") + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', + 'deleteOriginPath', + args=args) - result = self.cdn_client.purge_content(12345, url) + def test_purge_content(self): + self.cdn_client.purge_content("12345", "/example1") - self.assertEqual(expected, result) + args = ("12345", + "/example1") + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge', + 'createPurge', + args=args) From 9ceccb10eb0ab4e142785942763dfb4cba265d57 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 10 Jun 2019 11:56:39 -0400 Subject: [PATCH 0110/1582] Refactor cdn network. --- SoftLayer/utils.py | 3 ++- tests/managers/cdn_tests.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index ac718593b..b70997842 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -327,11 +327,12 @@ def timestamp(date): def days_to_datetime(days): - """ Returns the datetime value of last N days. + """Returns the datetime value of last N days. :param int days: From 0 to N days :returns int: The datetime of last N days or datetime.now() if days <= 0. """ + date = datetime.datetime.now() if days > 0: diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 988819830..b35cfbd37 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,9 +5,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.managers import cdn from SoftLayer import testing from SoftLayer import utils -from SoftLayer.managers import cdn class CDNTests(testing.TestCase): From f1838f7c7d2a2c3c94e0d9182e18fb504c77f5e2 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 10 Jun 2019 19:06:54 -0400 Subject: [PATCH 0111/1582] Refactor cdn network. --- SoftLayer/CLI/cdn/load.py | 18 -------- ...nMarketplace_Configuration_Mapping_Path.py | 2 + ...ftLayer_Network_ContentDelivery_Account.py | 41 ------------------- SoftLayer/managers/cdn.py | 4 +- tests/CLI/modules/cdn_tests.py | 28 +++++++++++-- tests/managers/cdn_tests.py | 24 ++++++++++- 6 files changed, 51 insertions(+), 66 deletions(-) delete mode 100644 SoftLayer/CLI/cdn/load.py delete mode 100644 SoftLayer/fixtures/SoftLayer_Network_ContentDelivery_Account.py diff --git a/SoftLayer/CLI/cdn/load.py b/SoftLayer/CLI/cdn/load.py deleted file mode 100644 index 648f4f34e..000000000 --- a/SoftLayer/CLI/cdn/load.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Cache one or more files on all edge nodes.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment - - -@click.command() -@click.argument('account_id') -@click.argument('content_url', nargs=-1) -@environment.pass_env -def cli(env, account_id, content_url): - """Cache one or more files on all edge nodes.""" - - manager = SoftLayer.CDNManager(env.client) - manager.load_content(account_id, content_url) diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py index 0bfa71375..59705f7ba 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path.py @@ -28,6 +28,8 @@ "originType": "HOST_SERVER", "path": "/example", "status": "RUNNING", + "bucketName": "test-bucket", + 'fileExtension': 'jpg', "performanceConfiguration": "General web delivery" } ] diff --git a/SoftLayer/fixtures/SoftLayer_Network_ContentDelivery_Account.py b/SoftLayer/fixtures/SoftLayer_Network_ContentDelivery_Account.py deleted file mode 100644 index 643df9c10..000000000 --- a/SoftLayer/fixtures/SoftLayer_Network_ContentDelivery_Account.py +++ /dev/null @@ -1,41 +0,0 @@ -getObject = { - "cdnAccountName": "1234a", - "providerPortalAccessFlag": False, - "createDate": "2012-06-25T14:05:28-07:00", - "id": 1234, - "legacyCdnFlag": False, - "dependantServiceFlag": True, - "cdnSolutionName": "ORIGIN_PULL", - "statusId": 4, - "accountId": 1234, - "status": {'name': 'ACTIVE'}, -} - -getOriginPullMappingInformation = [ - { - "originUrl": "http://ams01.objectstorage.softlayer.net:80", - "mediaType": "FLASH", - "id": "12345", - "isSecureContent": False - }, - { - "originUrl": "http://sng01.objectstorage.softlayer.net:80", - "mediaType": "FLASH", - "id": "12345", - "isSecureContent": False - } -] - -createOriginPullMapping = True - -deleteOriginPullRule = True - -loadContent = True - -purgeContent = [ - {'url': 'http://z/img/0z020.png', 'statusCode': 'SUCCESS'}, - {'url': 'http://y/img/0z010.png', 'statusCode': 'FAILED'}, - {'url': 'http://', 'statusCode': 'INVALID_URL'} -] - -purgeCache = [{'url': 'http://example.com', 'statusCode': 'SUCCESS'}] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index f39b9afb6..c203996a1 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -96,7 +96,7 @@ def add_origin(self, unique_id, origin, path, origin_type="server", header=None, 'uniqueId': unique_id, 'path': path, 'origin': origin, - 'originType': types.get(origin_type, 'HOST_SERVER'), + 'originType': types.get(origin_type), 'httpPort': port, 'protocol': protocol.upper(), 'performanceConfiguration': performance_config.get(optimize_for, 'General web delivery'), @@ -109,8 +109,6 @@ def add_origin(self, unique_id, origin, path, origin_type="server", header=None, if types.get(origin_type) == 'OBJECT_STORAGE': if bucket_name: new_origin['bucketName'] = bucket_name - else: - raise exceptions.SoftLayerError("Bucket name is required when the origin type is OBJECT_STORAGE") if file_extensions: new_origin['fileExtension'] = file_extensions diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index c1427f22e..c2c030a0c 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -7,6 +7,7 @@ import json from SoftLayer import testing +from SoftLayer.CLI import exceptions class CdnTests(testing.TestCase): @@ -61,9 +62,30 @@ def test_list_origins(self): 'Path': '/example1', 'Status': 'RUNNING'}]) - def test_add_origin(self): - result = self.run_command(['cdn', 'origin-add', '-H=test.example.com', '-p', 80, '-o', 'web', '-c=include-all', - '1234', '10.10.10.1', '/example/videos2']) + def test_add_origin_server(self): + result = self.run_command( + ['cdn', 'origin-add', '-t', 'server', '-H=test.example.com', '-p', 80, '-o', 'web', '-c=include-all', + '1234', '10.10.10.1', '/example/videos2']) + + self.assert_no_fail(result) + + def test_add_origin_storage(self): + result = self.run_command(['cdn', 'origin-add', '-t', 'storage', '-b=test-bucket', '-H=test.example.com', + '-p', 80, '-o', 'web', '-c=include-all', '1234', '10.10.10.1', '/example/videos2']) + + self.assert_no_fail(result) + + def test_add_origin_without_storage(self): + result = self.run_command(['cdn', 'origin-add', '-t', 'storage', '-H=test.example.com', '-p', 80, + '-o', 'web', '-c=include-all', '1234', '10.10.10.1', '/example/videos2']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.ArgumentError) + + def test_add_origin_storage_with_file_extensions(self): + result = self.run_command( + ['cdn', 'origin-add', '-t', 'storage', '-b=test-bucket', '-e', 'jpg', '-H=test.example.com', '-p', 80, + '-o', 'web', '-c=include-all', '1234', '10.10.10.1', '/example/videos2']) self.assert_no_fail(result) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index b35cfbd37..24f73d5f1 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,9 +5,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.managers import cdn from SoftLayer import testing from SoftLayer import utils +from SoftLayer.managers import cdn class CDNTests(testing.TestCase): @@ -70,6 +70,28 @@ def test_add_origin(self): 'createOriginPath', args=args) + def test_add_origin_with_bucket_and_file_extension(self): + self.cdn_client.add_origin("12345", "10.10.10.1", "/example/videos", origin_type="storage", + bucket_name="test-bucket", file_extensions="jpg", header="test.example.com", port=80, + protocol='http', optimize_for="web", cache_query="include all") + + args = ({ + 'uniqueId': "12345", + 'origin': '10.10.10.1', + 'path': '/example/videos', + 'originType': 'OBJECT_STORAGE', + 'header': 'test.example.com', + 'httpPort': 80, + 'protocol': 'HTTP', + 'bucketName': 'test-bucket', + 'fileExtension': 'jpg', + 'performanceConfiguration': 'General web delivery', + 'cacheKeyQueryRule': "include all" + },) + self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', + 'createOriginPath', + args=args) + def test_remove_origin(self): self.cdn_client.remove_origin("12345", "/example1") From 721442db58457c7f17416f70b13b59b49174aff1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 11 Jun 2019 18:03:06 -0400 Subject: [PATCH 0112/1582] Add cdn documentation and fix tox analysis. --- SoftLayer/CLI/routes.py | 1 - SoftLayer/managers/cdn.py | 1 - docs/cli/cdn.rst | 29 +++++++++++++++++++++++++++++ tests/CLI/modules/cdn_tests.py | 2 +- tests/managers/cdn_tests.py | 2 +- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 docs/cli/cdn.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 62c1baa47..5524b6948 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -54,7 +54,6 @@ ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), ('cdn:list', 'SoftLayer.CLI.cdn.list:cli'), - ('cdn:load', 'SoftLayer.CLI.cdn.load:cli'), ('cdn:origin-add', 'SoftLayer.CLI.cdn.origin_add:cli'), ('cdn:origin-list', 'SoftLayer.CLI.cdn.origin_list:cli'), ('cdn:origin-remove', 'SoftLayer.CLI.cdn.origin_remove:cli'), diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index c203996a1..b246b923d 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -6,7 +6,6 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions from SoftLayer import utils diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst new file mode 100644 index 000000000..fce54f731 --- /dev/null +++ b/docs/cli/cdn.rst @@ -0,0 +1,29 @@ +.. _cli_cdn: + +Interacting with CDN +============================== + + +.. click:: SoftLayer.CLI.cdn.detail:cli + :prog: cdn detail + :show-nested: + +.. click:: SoftLayer.CLI.cdn.list:cli + :prog: cdn list + :show-nested: + +.. click:: SoftLayer.CLI.cdn.origin_add:cli + :prog: cdn origin-add + :show-nested: + +.. click:: SoftLayer.CLI.cdn.origin_list:cli + :prog: cdn origin-list + :show-nested: + +.. click:: SoftLayer.CLI.cdn.origin_remove:cli + :prog: cdn origin-remove + :show-nested: + +.. click:: SoftLayer.CLI.cdn.purge:cli + :prog: cdn purge + :show-nested: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index c2c030a0c..abdb80df8 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -6,8 +6,8 @@ """ import json -from SoftLayer import testing from SoftLayer.CLI import exceptions +from SoftLayer import testing class CdnTests(testing.TestCase): diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 24f73d5f1..67527ade3 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,9 +5,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.managers import cdn from SoftLayer import testing from SoftLayer import utils -from SoftLayer.managers import cdn class CDNTests(testing.TestCase): From 0f409bee9a8dda7defd949aeac905ba442f32f59 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 14 Jun 2019 17:13:57 -0400 Subject: [PATCH 0113/1582] Refactor code review. --- SoftLayer/CLI/cdn/detail.py | 18 +++++++++--------- SoftLayer/CLI/cdn/origin_add.py | 6 +++++- SoftLayer/CLI/cdn/purge.py | 3 +++ SoftLayer/managers/cdn.py | 6 +++--- tests/CLI/modules/cdn_tests.py | 2 +- tests/managers/cdn_tests.py | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/cdn/detail.py b/SoftLayer/CLI/cdn/detail.py index fe067d5b3..ef93d2794 100644 --- a/SoftLayer/CLI/cdn/detail.py +++ b/SoftLayer/CLI/cdn/detail.py @@ -10,22 +10,22 @@ @click.command() @click.argument('unique_id') -@click.option('--last_days', - default=30, - help='cdn overview last days less than 90 days, because it is the maximum e.g 7, 15, 30, 60, 89') +@click.option('--history', + default=30, type=click.IntRange(1, 89), + help='Bandwidth, Hits, Ratio counted over history number of days ago. 89 is the maximum. ') @environment.pass_env -def cli(env, unique_id, last_days): +def cli(env, unique_id, history): """Detail a CDN Account.""" manager = SoftLayer.CDNManager(env.client) cdn_mapping = manager.get_cdn(unique_id) - cdn_metrics = manager.get_usage_metrics(unique_id, days=last_days) + cdn_metrics = manager.get_usage_metrics(unique_id, history=history) # usage metrics - total_bandwidth = str(cdn_metrics['totals'][0]) + " GB" - total_hits = str(cdn_metrics['totals'][1]) - hit_radio = str(cdn_metrics['totals'][2]) + " %" + total_bandwidth = "%s GB" % cdn_metrics['totals'][0] + total_hits = cdn_metrics['totals'][1] + hit_ratio = "%s %%" % cdn_metrics['totals'][2] table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -41,6 +41,6 @@ def cli(env, unique_id, last_days): table.add_row(['status', cdn_mapping['status']]) table.add_row(['total_bandwidth', total_bandwidth]) table.add_row(['total_hits', total_hits]) - table.add_row(['hit_radio', hit_radio]) + table.add_row(['hit_radio', hit_ratio]) env.fout(table) diff --git a/SoftLayer/CLI/cdn/origin_add.py b/SoftLayer/CLI/cdn/origin_add.py index 413b9c446..08790d9b7 100644 --- a/SoftLayer/CLI/cdn/origin_add.py +++ b/SoftLayer/CLI/cdn/origin_add.py @@ -51,7 +51,11 @@ @environment.pass_env def cli(env, unique_id, origin, path, origin_type, header, bucket_name, port, protocol, optimize_for, extensions, cache_query): - """Create an origin path for an existing CDN mapping.""" + """Create an origin path for an existing CDN mapping. + + For more information see the following documentation: \n + https://cloud.ibm.com/docs/infrastructure/CDN?topic=CDN-manage-your-cdn#adding-origin-path-details + """ manager = SoftLayer.CDNManager(env.client) diff --git a/SoftLayer/CLI/cdn/purge.py b/SoftLayer/CLI/cdn/purge.py index d029aba56..26bff1dd2 100644 --- a/SoftLayer/CLI/cdn/purge.py +++ b/SoftLayer/CLI/cdn/purge.py @@ -17,6 +17,9 @@ def cli(env, unique_id, path): Example: slcli cdn purge 9779455 /article/file.txt + + For more information see the following documentation: \n + https://cloud.ibm.com/docs/infrastructure/CDN?topic=CDN-manage-your-cdn#purging-cached-content """ manager = SoftLayer.CDNManager(env.client) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index b246b923d..51dfb5252 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -137,18 +137,18 @@ def purge_content(self, unique_id, path): return self.cdn_purge.createPurge(unique_id, path) - def get_usage_metrics(self, unique_id, days=30, frequency="aggregate"): + def get_usage_metrics(self, unique_id, history=30, frequency="aggregate"): """Retrieves the cdn usage metrics. It uses the 'days' argument if start_date and end_date are None. :param int unique_id: The CDN uniqueId from which the usage metrics will be obtained. - :param int days: Last N days, default days is 30. + :param int history: Last N days, default days is 30. :param str frequency: It can be day, week, month and aggregate. The default is "aggregate". :returns: A Container_Network_CdnMarketplace_Metrics object """ - _start = utils.days_to_datetime(days) + _start = utils.days_to_datetime(history) _end = utils.days_to_datetime(0) _start_date = utils.timestamp(_start) diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index abdb80df8..cb3c59e43 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -26,7 +26,7 @@ def test_list_accounts(self): ) def test_detail_account(self): - result = self.run_command(['cdn', 'detail', '--last_days=30', '1245']) + result = self.run_command(['cdn', 'detail', '--history=30', '1245']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 67527ade3..41a675c6d 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -29,7 +29,7 @@ def test_detail_cdn(self): args=args) def test_detail_usage_metric(self): - self.cdn_client.get_usage_metrics(12345, days=30, frequency="aggregate") + self.cdn_client.get_usage_metrics(12345, history=30, frequency="aggregate") _start = utils.days_to_datetime(30) _end = utils.days_to_datetime(0) From 02507141591f6365425a5c2ebf7bca789674c7e1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 17 Jun 2019 15:08:26 -0500 Subject: [PATCH 0114/1582] base for using IBMid as authentication --- SoftLayer/CLI/config/setup.py | 7 +++++-- SoftLayer/CLI/hardware/bandwidth.py | 2 +- SoftLayer/auth.py | 15 +++++++++++---- SoftLayer/transports.py | 11 ++++++++++- docs/cli.rst | 2 ++ docs/cli/config.rst | 18 ++++++++++++++++++ docs/config_file.rst | 10 ++++++++++ 7 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 docs/cli/config.rst diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 54aa1c79e..c984d569e 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -19,7 +19,7 @@ def get_api_key(client, username, secret): """ # Try to use a client with username/api key - if len(secret) == 64: + if len(secret) == 64 or username == 'apikey': try: client['Account'].getCurrentUser() return secret @@ -40,7 +40,10 @@ def get_api_key(client, username, secret): @click.command() @environment.pass_env def cli(env): - """Edit configuration.""" + """Setup the ~/.softlayer file with username and apikey. + + Set the username to 'apikey' for cloud.ibm.com accounts. + """ username, secret, endpoint_url, timeout = get_user_input(env) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) diff --git a/SoftLayer/CLI/hardware/bandwidth.py b/SoftLayer/CLI/hardware/bandwidth.py index efe821c29..382615052 100644 --- a/SoftLayer/CLI/hardware/bandwidth.py +++ b/SoftLayer/CLI/hardware/bandwidth.py @@ -1,4 +1,4 @@ -"""Get details for a hardware device.""" +"""GBandwidth data over date range. Bandwidth is listed in GB""" # :license: MIT, see LICENSE for more details. import click diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 57a911e79..4046937e6 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -73,10 +73,17 @@ def __init__(self, username, api_key): def get_request(self, request): """Sets token-based auth headers.""" - request.headers['authenticate'] = { - 'username': self.username, - 'apiKey': self.api_key, - } + + # See https://cloud.ibm.com/docs/iam?topic=iam-iamapikeysforservices for why this is the way it is + if self.username == 'apikey': + request.transport_user = self.username + request.transport_password = self.api_key + else: + request.headers['authenticate'] = { + 'username': self.username, + 'apiKey': self.api_key, + } + return request def __repr__(self): diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 56eb14e7c..b4790c60e 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -170,6 +170,10 @@ def __call__(self, request): 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} @@ -208,6 +212,7 @@ def __call__(self, request): try: resp = self.client.request('POST', request.url, data=request.payload, + auth=auth, headers=request.transport_headers, timeout=self.timeout, verify=request.verify, @@ -253,6 +258,7 @@ def print_reproduceable(self, request): from string import Template 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 @@ -261,6 +267,9 @@ def print_reproduceable(self, request): 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 @@ -269,7 +278,7 @@ def print_reproduceable(self, request): cert = $cert proxy = $proxy response = client.request('POST', url, data=payload, headers=transport_headers, timeout=timeout, - verify=verify, cert=cert, proxies=proxy) + verify=verify, cert=cert, proxies=proxy, auth=auth) xml = ElementTree.fromstring(response.content) ElementTree.dump(xml) ==========================''') diff --git a/docs/cli.rst b/docs/cli.rst index 307592fbd..7b09fdc17 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -48,6 +48,8 @@ To check the configuration, you can use `slcli config show`. :..............:..................................................................: +If you are using an account created from the https://cloud.ibm.com portal, your username will be literally `apikey`, and use the key provided. `How to create an IBM apikey `_ + To see more about the config file format, see :ref:`config_file`. .. _usage-examples: diff --git a/docs/cli/config.rst b/docs/cli/config.rst new file mode 100644 index 000000000..b49e5d5ad --- /dev/null +++ b/docs/cli/config.rst @@ -0,0 +1,18 @@ +.. _cli_config: + +Config +======== + +`Creating an IBMID apikey `_ +`IBMid for services `_ + +`Creating a SoftLayer apikey `_ + +.. click:: SoftLayer.CLI.config.setup:cli + :prog: config setup + :show-nested: + + +.. click:: SoftLayer.CLI.config.show:cli + :prog: config show + :show-nested: diff --git a/docs/config_file.rst b/docs/config_file.rst index ecea6364d..57ecdc1d1 100644 --- a/docs/config_file.rst +++ b/docs/config_file.rst @@ -25,3 +25,13 @@ automatically by the `slcli setup` command detailed here: api_key = oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha endpoint_url = https://api.softlayer.com/xmlrpc/v3/ timeout = 40 + + +*Cloud.ibm.com Config Example* +:: + + [softlayer] + username = apikey + api_key = 123cNyhzg45Ab6789ADyzwR_2LAagNVbySgY73tAQOz1 + endpoint_url = https://api.softlayer.com/rest/v3.1/ + timeout = 40 \ No newline at end of file From 37696f05e456fcb1d3849e31b4bc12982ea8f986 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 17 Jun 2019 15:34:31 -0500 Subject: [PATCH 0115/1582] unit test for new code --- tests/transport_tests.py | 53 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index a14ec9238..e7a71a6fa 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -78,7 +78,8 @@ def test_call(self, request): data=data, timeout=None, cert=None, - verify=True) + verify=True, + auth=None) self.assertEqual(resp, []) self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) @@ -114,7 +115,8 @@ def test_valid_proxy(self, request): headers=mock.ANY, timeout=None, cert=None, - verify=True) + verify=True, + auth=None) @mock.patch('SoftLayer.transports.requests.Session.request') def test_identifier(self, request): @@ -264,6 +266,50 @@ 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('requests.auth.HTTPBasicAuth') + def test_ibm_id_call(self, auth, request): + request.return_value = self.response + + data = ''' + +getObject + + + + +headers + + + + + + + +''' + + 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://something.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( @@ -311,7 +357,8 @@ def test_verify(request, cert=mock.ANY, proxies=mock.ANY, timeout=mock.ANY, - verify=expected) + verify=expected, + auth=None) class TestRestAPICall(testing.TestCase): From edeff993c9728226047082f13566e2d117685abe Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 17 Jun 2019 16:06:36 -0500 Subject: [PATCH 0116/1582] improved help message for invoices --- SoftLayer/CLI/account/invoice_detail.py | 2 +- SoftLayer/CLI/account/invoices.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index 45343184e..b840f3f60 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -15,7 +15,7 @@ help="Shows a very detailed list of charges") @environment.pass_env def cli(env, identifier, details): - """Invoices and all that mess""" + """Invoice details""" manager = AccountManager(env.client) top_items = manager.get_billing_items(identifier) diff --git a/SoftLayer/CLI/account/invoices.py b/SoftLayer/CLI/account/invoices.py index 1610ed11e..0e1b2a59f 100644 --- a/SoftLayer/CLI/account/invoices.py +++ b/SoftLayer/CLI/account/invoices.py @@ -18,7 +18,7 @@ help="Return ALL invoices. There may be a lot of these.") @environment.pass_env def cli(env, limit, closed=False, get_all=False): - """Invoices and all that mess""" + """List invoices""" manager = AccountManager(env.client) invoices = manager.get_invoices(limit, closed, get_all) From 9945b63c66c7199d5c778b5cbb5d077656f16069 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 17 Jun 2019 23:59:08 -0500 Subject: [PATCH 0117/1582] Initial transient support --- SoftLayer/CLI/virt/create.py | 11 ++++++++++- SoftLayer/managers/vs.py | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 631793475..d985bb7f8 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -33,6 +33,7 @@ def _update_with_like_args(ctx, _, value): 'dedicated': like_details['dedicatedAccountHostOnlyFlag'], 'private': like_details['privateNetworkOnlyFlag'], 'placement_id': like_details.get('placementGroupId', None), + 'transient': like_details['transientGuestFlag'] or None, } like_args['flavor'] = utils.lookup(like_details, @@ -83,6 +84,7 @@ def _parse_create_args(client, args): "domain": args.get('domain', None), "host_id": args.get('host_id', None), "private": args.get('private', None), + "transient": args.get('transient', None), "hostname": args.get('hostname', None), "nic_speed": args.get('network', None), "boot_mode": args.get('boot_mode', None), @@ -105,7 +107,8 @@ def _parse_create_args(client, args): if args.get('image'): if args.get('image').isdigit(): image_mgr = SoftLayer.ImageManager(client) - image_details = image_mgr.get_image(args.get('image'), mask="id,globalIdentifier") + image_details = image_mgr.get_image(args.get('image'), + mask="id,globalIdentifier") data['image_id'] = image_details['globalIdentifier'] else: data['image_id'] = args['image'] @@ -198,6 +201,8 @@ def _parse_create_args(client, args): @click.option('--placementgroup', help="Placement Group name or Id to order this guest on. See: slcli vs placementgroup list") @click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") +@click.option('--transient', is_flag=True, + help="Provisions the VS to be transient") @environment.pass_env def cli(env, **args): """Order/create virtual servers.""" @@ -289,6 +294,10 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[-h | --host-id] not allowed with [-f | --flavor]') + if all([args['dedicated'], args['transient']]): + raise exceptions.ArgumentError( + '[--dedicated | --public] not allowed with [--transient]') + if all([args['userdata'], args['userfile']]): raise exceptions.ArgumentError( '[-u | --userdata] not allowed with [-F | --userfile]') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 03ac6d035..b070406cd 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -312,7 +312,7 @@ def _generate_create_dict( private_subnet=None, public_subnet=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, - private_security_groups=None, boot_mode=None, **kwargs): + private_security_groups=None, boot_mode=None, transient=False, **kwargs): """Returns a dict appropriate to pass into Virtual_Guest::createObject See :func:`create_instance` for a list of available options. @@ -505,6 +505,7 @@ def verify_create_instance(self, **kwargs): 'flavor': 'BL1_1X2X100' 'dedicated': False, 'private': False, + 'transient': False, 'os_code' : u'UBUNTU_LATEST', 'hourly': True, 'ssh_keys': [1234], @@ -883,6 +884,7 @@ def order_guest(self, guest_object, test=False): 'flavor': 'BL1_1X2X100' 'dedicated': False, 'private': False, + 'transient': False, 'os_code' : u'UBUNTU_LATEST', 'hourly': True, 'ssh_keys': [1234], From 18cd6e03aaab2e3ef1e2168fbaf1cb8cc61ce797 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 2 Jul 2019 21:58:43 -0500 Subject: [PATCH 0118/1582] Add vs list filtering. Handle create_instance transient option. Require transient with hourly in the CLI. Default to hourly if only the transient option is present. --- SoftLayer/CLI/virt/create.py | 10 +++++++++- SoftLayer/CLI/virt/detail.py | 1 + SoftLayer/managers/vs.py | 12 +++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index d985bb7f8..d39609a7e 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -142,6 +142,10 @@ def _parse_create_args(client, args): if args.get('host_id'): data['host_id'] = args['host_id'] + if args.get('transient') and not args.get('billing'): + # No billing type specified and transient, so default to hourly + data['hourly'] = True + if args.get('placementgroup'): resolver = SoftLayer.managers.PlacementManager(client).resolve_ids data['placement_id'] = helpers.resolve_id(resolver, args.get('placementgroup'), 'PlacementGroup') @@ -296,7 +300,11 @@ def _validate_args(env, args): if all([args['dedicated'], args['transient']]): raise exceptions.ArgumentError( - '[--dedicated | --public] not allowed with [--transient]') + '[--dedicated] not allowed with [--transient]') + + if args['transient'] and not args['hourly']: + raise exceptions.ArgumentError( + '[--transient] not allowed with [--billing monthly]') if all([args['userdata'], args['userfile']]): raise exceptions.ArgumentError( diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index feb03cf82..2c9771b21 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -61,6 +61,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) + table.add_row(['transient', result['transientGuestFlag']]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b070406cd..85bbdf9d9 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -61,7 +61,7 @@ def __init__(self, client, ordering_manager=None): def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, memory=None, hostname=None, domain=None, local_disk=None, datacenter=None, nic_speed=None, - public_ip=None, private_ip=None, **kwargs): + public_ip=None, private_ip=None, transient=None, **kwargs): """Retrieve a list of all virtual servers on the account. Example:: @@ -88,6 +88,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, :param integer nic_speed: filter based on network speed (in MBPS) :param string public_ip: filter based on public ip address :param string private_ip: filter based on private ip address + :param boolean transient: filter on transient or non-transient instances :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) :returns: Returns a list of dictionaries representing the matching virtual servers @@ -157,6 +158,11 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, _filter['virtualGuests']['primaryBackendIpAddress'] = ( utils.query_filter(private_ip)) + if transient is not None: + _filter['virtualGuests']['transientGuestFlag'] = ( + utils.query_filter(bool(transient)) + ) + kwargs['filter'] = _filter.to_dict() kwargs['iter'] = True return self.client.call('Account', call, **kwargs) @@ -194,6 +200,7 @@ def get_instance(self, instance_id, **kwargs): 'provisionDate,' 'notes,' 'dedicatedAccountHostOnlyFlag,' + 'transientGuestFlag,' 'privateNetworkOnlyFlag,' 'primaryBackendIpAddress,' 'primaryIpAddress,' @@ -362,6 +369,9 @@ def _generate_create_dict( if private: data['privateNetworkOnlyFlag'] = private + if transient: + data['transientGuestFlag'] = transient + if image_id: data["blockDeviceTemplateGroup"] = {"globalIdentifier": image_id} elif os_code: From 6ccdcbd7fe2ed28af89a13e7b074650908184209 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 10 Jul 2019 16:49:28 -0500 Subject: [PATCH 0119/1582] removed legacy LB commands, added LBaaS and Netscaler support --- SoftLayer/CLI/loadbal/__init__.py | 10 -- SoftLayer/CLI/loadbal/cancel.py | 29 ----- SoftLayer/CLI/loadbal/create.py | 25 ---- SoftLayer/CLI/loadbal/create_options.py | 35 ----- SoftLayer/CLI/loadbal/detail.py | 157 ++++++++++++++--------- SoftLayer/CLI/loadbal/group_add.py | 41 ------ SoftLayer/CLI/loadbal/group_delete.py | 28 ---- SoftLayer/CLI/loadbal/group_edit.py | 43 ------- SoftLayer/CLI/loadbal/group_reset.py | 21 --- SoftLayer/CLI/loadbal/health.py | 20 +++ SoftLayer/CLI/loadbal/health_checks.py | 26 ---- SoftLayer/CLI/loadbal/list.py | 74 ++++++----- SoftLayer/CLI/loadbal/ns_detail.py | 49 +++++++ SoftLayer/CLI/loadbal/ns_list.py | 46 +++++++ SoftLayer/CLI/loadbal/routing_methods.py | 25 ---- SoftLayer/CLI/loadbal/routing_types.py | 24 ---- SoftLayer/CLI/loadbal/service_add.py | 53 -------- SoftLayer/CLI/loadbal/service_delete.py | 28 ---- SoftLayer/CLI/loadbal/service_edit.py | 52 -------- SoftLayer/CLI/loadbal/service_toggle.py | 28 ---- SoftLayer/CLI/routes.py | 19 +-- SoftLayer/managers/load_balancer.py | 61 +++++++-- SoftLayer/utils.py | 2 + 23 files changed, 311 insertions(+), 585 deletions(-) delete mode 100644 SoftLayer/CLI/loadbal/cancel.py delete mode 100644 SoftLayer/CLI/loadbal/create.py delete mode 100644 SoftLayer/CLI/loadbal/create_options.py delete mode 100644 SoftLayer/CLI/loadbal/group_add.py delete mode 100644 SoftLayer/CLI/loadbal/group_delete.py delete mode 100644 SoftLayer/CLI/loadbal/group_edit.py delete mode 100644 SoftLayer/CLI/loadbal/group_reset.py create mode 100644 SoftLayer/CLI/loadbal/health.py delete mode 100644 SoftLayer/CLI/loadbal/health_checks.py create mode 100644 SoftLayer/CLI/loadbal/ns_detail.py create mode 100644 SoftLayer/CLI/loadbal/ns_list.py delete mode 100644 SoftLayer/CLI/loadbal/routing_methods.py delete mode 100644 SoftLayer/CLI/loadbal/routing_types.py delete mode 100644 SoftLayer/CLI/loadbal/service_add.py delete mode 100644 SoftLayer/CLI/loadbal/service_delete.py delete mode 100644 SoftLayer/CLI/loadbal/service_edit.py delete mode 100644 SoftLayer/CLI/loadbal/service_toggle.py diff --git a/SoftLayer/CLI/loadbal/__init__.py b/SoftLayer/CLI/loadbal/__init__.py index 8f7becb62..77d12e33d 100644 --- a/SoftLayer/CLI/loadbal/__init__.py +++ b/SoftLayer/CLI/loadbal/__init__.py @@ -1,12 +1,2 @@ """Load balancers.""" -from SoftLayer.CLI import exceptions - - -def parse_id(input_id): - """Parse the load balancer kind and actual id from the "kind:id" form.""" - parts = input_id.split(':') - if len(parts) != 2: - raise exceptions.CLIAbort( - 'Invalid ID %s: ID should be of the form "kind:id"' % input_id) - return parts[0], int(parts[1]) diff --git a/SoftLayer/CLI/loadbal/cancel.py b/SoftLayer/CLI/loadbal/cancel.py deleted file mode 100644 index 68e3e62ca..000000000 --- a/SoftLayer/CLI/loadbal/cancel.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Cancel an existing load balancer.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Cancel an existing load balancer.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - _, loadbal_id = loadbal.parse_id(identifier) - - if not (env.skip_confirmations or - formatting.confirm("This action will cancel a load balancer. " - "Continue?")): - raise exceptions.CLIAbort('Aborted.') - - mgr.cancel_lb(loadbal_id) - env.fout('Load Balancer with id %s is being cancelled!' % identifier) diff --git a/SoftLayer/CLI/loadbal/create.py b/SoftLayer/CLI/loadbal/create.py deleted file mode 100644 index e32cc6297..000000000 --- a/SoftLayer/CLI/loadbal/create.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Adds a load balancer given the id returned from create-options.""" -# :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 - - -@click.command() -@click.argument('billing-id') -@click.option('--datacenter', '-d', - help='Datacenter shortname (sng01, dal05, ...)') -@environment.pass_env -def cli(env, billing_id, datacenter): - """Adds a load balancer given the id returned from create-options.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - if not formatting.confirm("This action will incur charges on your " - "account. Continue?"): - raise exceptions.CLIAbort('Aborted.') - mgr.add_local_lb(billing_id, datacenter=datacenter) - env.fout("Load balancer is being created!") diff --git a/SoftLayer/CLI/loadbal/create_options.py b/SoftLayer/CLI/loadbal/create_options.py deleted file mode 100644 index b4392b109..000000000 --- a/SoftLayer/CLI/loadbal/create_options.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Show load balancer options.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """Get price options to create a load balancer with.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - table = formatting.Table(['price_id', 'capacity', 'description', 'price']) - - table.sortby = 'price' - table.align['price'] = 'r' - table.align['capacity'] = 'r' - table.align['id'] = 'r' - - packages = mgr.get_lb_pkgs() - - for package in packages: - table.add_row([ - package['prices'][0]['id'], - package.get('capacity'), - package['description'], - '%.2f' % float(package['prices'][0]['recurringFee']) - ]) - - env.fout(table) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index c8711fbf5..6f29e7655 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -1,81 +1,122 @@ -"""Get Load balancer details.""" -# :license: MIT, see LICENSE for more details. - +"""Get Load Balancer as a Service details.""" import click import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.CLI import loadbal - +from SoftLayer import utils +from pprint import pprint as pp @click.command() @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get Load balancer details.""" + """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - _, loadbal_id = loadbal.parse_id(identifier) + lb = mgr.get_lb(identifier) + pp(lb) + table = lbaas_table(lb) + + env.fout(table) - load_balancer = mgr.get_local_lb(loadbal_id) +def lbaas_table(lb): + """Generates a table from a list of LBaaS devices""" table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'l' + table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['ID', 'local:%s' % load_balancer['id']]) - table.add_row(['IP Address', load_balancer['ipAddress']['ipAddress']]) - name = load_balancer['loadBalancerHardware'][0]['datacenter']['name'] - table.add_row(['Datacenter', name]) - table.add_row(['Connections limit', load_balancer['connectionLimit']]) - table.add_row(['Dedicated', load_balancer['dedicatedFlag']]) - table.add_row(['HA', load_balancer['highAvailabilityFlag']]) - table.add_row(['SSL Enabled', load_balancer['sslEnabledFlag']]) - table.add_row(['SSL Active', load_balancer['sslActiveFlag']]) + table.add_row(['Id', lb.get('id')]) + table.add_row(['UUI', lb.get('uuid')]) + table.add_row(['Address', lb.get('address')]) + table.add_row(['Location', utils.lookup(lb, 'datacenter', 'longName')]) + table.add_row(['Description', lb.get('description')]) + table.add_row(['Name', lb.get('name')]) + table.add_row(['Status', "{} / {}".format(lb.get('provisioningStatus'), lb.get('operatingStatus'))]) - index0 = 1 - for virtual_server in load_balancer['virtualServers']: - for group in virtual_server['serviceGroups']: - service_group_table = formatting.KeyValueTable(['name', 'value']) + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ + hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) + for hp in lb.get('healthMonitors', []): + hp_table.add_row([ + hp.get('uuid'), + hp.get('interval'), + hp.get('maxRetries'), + hp.get('monitorType'), + hp.get('timeout'), + utils.clean_time(hp.get('modifyDate')), + hp.get('provisioningStatus') + ]) + table.add_row(['Checks', hp_table]) - table.add_row(['Service Group %s' % index0, service_group_table]) - index0 += 1 + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ + l7_table = formatting.Table(['UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active' ]) + for l7 in lb.get('l7Pools', []): + l7_table.add_row([ + l7.get('uuid'), + l7.get('loadBalancingAlgorithm'), + l7.get('name'), + l7.get('protocol'), + utils.clean_time(l7.get('modifyDate')), + l7.get('provisioningStatus') + ]) + table.add_row(['L7 Pools', l7_table]) - service_group_table.add_row(['Guest ID', - virtual_server['id']]) - service_group_table.add_row(['Port', virtual_server['port']]) - service_group_table.add_row(['Allocation', - '%s %%' % - virtual_server['allocation']]) - service_group_table.add_row(['Routing Type', - '%s:%s' % - (group['routingTypeId'], - group['routingType']['name'])]) - service_group_table.add_row(['Routing Method', - '%s:%s' % - (group['routingMethodId'], - group['routingMethod']['name'])]) + pools = {} + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ + listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) + for listener in lb.get('listeners', []): + pool = listener.get('defaultPool') + priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) + pools[pool['uuid']] = priv_map + mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) + pool_table = formatting.Table(['Address', ]) + listener_table.add_row([ + listener.get('uuid'), + listener.get('connectionLimit', 'None'), + mapping, + pool.get('loadBalancingAlgorithm', 'None'), + utils.clean_time(listener.get('modifyDate')), + listener.get('provisioningStatus') + ]) + table.add_row(['Pools', listener_table]) - index1 = 1 - for service in group['services']: - service_table = formatting.KeyValueTable(['name', 'value']) + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ + member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] + for uuid in pools.keys(): + member_col.append(pools[uuid]) + member_table = formatting.Table(member_col) + for member in lb.get('members', []): + row = [ + member.get('uuid'), + member.get('address'), + member.get('weight'), + utils.clean_time(member.get('modifyDate')), + member.get('provisioningStatus') + ] + for uuid in pools.keys(): + row.append(getMemberHp(lb.get('health'), member.get('uuid'), uuid)) + member_table.add_row(row) + table.add_row(['Members',member_table]) - service_group_table.add_row(['Service %s' % index1, - service_table]) - index1 += 1 + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_SSLCipher/ + ssl_table = formatting.Table(['Id', 'Name']) + for ssl in lb.get('sslCiphers', []): + ssl_table.add_row([ssl.get('id'), ssl.get('name')]) + table.add_row(['Ciphers', ssl_table]) + return table - health_check = service['healthChecks'][0] - service_table.add_row(['Service ID', service['id']]) - service_table.add_row(['IP Address', - service['ipAddress']['ipAddress']]) - service_table.add_row(['Port', service['port']]) - service_table.add_row(['Health Check', - '%s:%s' % - (health_check['healthCheckTypeId'], - health_check['type']['name'])]) - service_table.add_row( - ['Weight', service['groupReferences'][0]['weight']]) - service_table.add_row(['Enabled', service['enabled']]) - service_table.add_row(['Status', service['status']]) +def getMemberHp(checks, member_uuid, pool_uuid): + """Helper function to find a members health in a given pool - env.fout(table) + :param checks list: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Pool/#healthMonitor + :param member_uuid: server UUID we are looking for + :param pool_uuid: Connection pool uuid to search for + """ + status = "---" + for check in checks: + if check.get('poolUuid') == pool_uuid: + for hp_member in check.get('membersHealth'): + if hp_member.get('uuid') == member_uuid: + status = hp_member.get('status') + + return status \ No newline at end of file diff --git a/SoftLayer/CLI/loadbal/group_add.py b/SoftLayer/CLI/loadbal/group_add.py deleted file mode 100644 index 202c6cab9..000000000 --- a/SoftLayer/CLI/loadbal/group_add.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Adds a new load_balancer service.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import loadbal - - -@click.command() -@click.argument('identifier') -@click.option('--allocation', - required=True, - type=click.INT, - help="The allocated percent of connections") -@click.option('--port', - required=True, - help="The port number", - type=click.INT) -@click.option('--routing-type', - required=True, - help="The port routing type") -@click.option('--routing-method', - required=True, - help="The routing method") -@environment.pass_env -def cli(env, identifier, allocation, port, routing_type, routing_method): - """Adds a new load_balancer service.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - _, loadbal_id = loadbal.parse_id(identifier) - - mgr.add_service_group(loadbal_id, - allocation=allocation, - port=port, - routing_type=routing_type, - routing_method=routing_method) - - env.fout('Load balancer service group is being added!') diff --git a/SoftLayer/CLI/loadbal/group_delete.py b/SoftLayer/CLI/loadbal/group_delete.py deleted file mode 100644 index 74980b7bd..000000000 --- a/SoftLayer/CLI/loadbal/group_delete.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Deletes an existing load balancer service group.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Deletes an existing load balancer service group.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - _, group_id = loadbal.parse_id(identifier) - - if not (env.skip_confirmations or - formatting.confirm("This action will cancel a service group. " - "Continue?")): - raise exceptions.CLIAbort('Aborted.') - - mgr.delete_service_group(group_id) - env.fout('Service group %s is being deleted!' % identifier) diff --git a/SoftLayer/CLI/loadbal/group_edit.py b/SoftLayer/CLI/loadbal/group_edit.py deleted file mode 100644 index c7ed92c87..000000000 --- a/SoftLayer/CLI/loadbal/group_edit.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Edit an existing load balancer service group.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@click.option('--allocation', - type=click.INT, - help="Change the allocated percent of connections") -@click.option('--port', - help="Change the port number", - type=click.INT) -@click.option('--routing-type', - help="Change the port routing type") -@click.option('--routing-method', - help="Change the routing method") -@environment.pass_env -def cli(env, identifier, allocation, port, routing_type, routing_method): - """Edit an existing load balancer service group.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - loadbal_id, group_id = loadbal.parse_id(identifier) - - # check if any input is provided - if not any([allocation, port, routing_type, routing_method]): - raise exceptions.CLIAbort( - 'At least one property is required to be changed!') - - mgr.edit_service_group(loadbal_id, - group_id, - allocation=allocation, - port=port, - routing_type=routing_type, - routing_method=routing_method) - - env.fout('Load balancer service group %s is being updated!' % identifier) diff --git a/SoftLayer/CLI/loadbal/group_reset.py b/SoftLayer/CLI/loadbal/group_reset.py deleted file mode 100644 index 093230633..000000000 --- a/SoftLayer/CLI/loadbal/group_reset.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Reset connections on a certain service group.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import loadbal - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Reset connections on a certain service group.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - loadbal_id, group_id = loadbal.parse_id(identifier) - - mgr.reset_service_group(loadbal_id, group_id) - env.fout('Load balancer service group connections are being reset!') diff --git a/SoftLayer/CLI/loadbal/health.py b/SoftLayer/CLI/loadbal/health.py new file mode 100644 index 000000000..d101b9b30 --- /dev/null +++ b/SoftLayer/CLI/loadbal/health.py @@ -0,0 +1,20 @@ +"""Manage LBaaS health checks.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils +from pprint import pprint as pp + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Manage LBaaS health checks.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + lb = mgr.get_lb(identifier) + table = lbaas_table(lb) + + env.fout(table) diff --git a/SoftLayer/CLI/loadbal/health_checks.py b/SoftLayer/CLI/loadbal/health_checks.py deleted file mode 100644 index 49a12a5cd..000000000 --- a/SoftLayer/CLI/loadbal/health_checks.py +++ /dev/null @@ -1,26 +0,0 @@ -"""List health check types.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """List health check types.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - hc_types = mgr.get_hc_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for hc_type in hc_types: - table.add_row([hc_type['id'], hc_type['name']]) - - env.fout(table) diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py index df44cc33e..e8394170c 100644 --- a/SoftLayer/CLI/loadbal/list.py +++ b/SoftLayer/CLI/loadbal/list.py @@ -1,49 +1,53 @@ -"""List active load balancers.""" -# :license: MIT, see LICENSE for more details. - +"""List active Load Balancer as a Service devices.""" import click import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting - +from SoftLayer import utils +from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): - """List active load balancers.""" + """List active Load Balancer as a Service devices.""" mgr = SoftLayer.LoadBalancerManager(env.client) - load_balancers = mgr.get_local_lbs() - - table = formatting.Table(['ID', - 'VIP Address', - 'Location', - 'SSL Offload', - 'Connections/second', - 'Type']) - - table.align['Connections/second'] = 'r' - - for load_balancer in load_balancers: - ssl_support = 'Not Supported' - if load_balancer['sslEnabledFlag']: - if load_balancer['sslActiveFlag']: - ssl_support = 'On' - else: - ssl_support = 'Off' - lb_type = 'Standard' - if load_balancer['dedicatedFlag']: - lb_type = 'Dedicated' - elif load_balancer['highAvailabilityFlag']: - lb_type = 'HA' + lbaas = mgr.get_lbaas() + if lbaas: + lbaas_table = generate_lbaas_table(lbaas) + env.fout(lbaas_table) + + else: + env.fout("No LBaaS devices found") + + +def location_sort(x): + """Quick function that just returns the datacenter longName for sorting""" + return utils.lookup(x, 'datacenter', 'longName') + + +def generate_lbaas_table(lbaas): + """Takes a list of SoftLayer_Network_LBaaS_LoadBalancer and makes a table""" + table = formatting.Table([ + 'Id', 'Location', 'Address', 'Description', 'Public', 'Create Date', 'Members', 'Listeners' + ], title="IBM Cloud LoadBalancer") + + table.align['Address'] = 'l' + table.align['Description'] = 'l' + table.align['Location'] = 'l' + for lb in sorted(lbaas,key=location_sort): table.add_row([ - 'local:%s' % load_balancer['id'], - load_balancer['ipAddress']['ipAddress'], - load_balancer['loadBalancerHardware'][0]['datacenter']['name'], - ssl_support, - load_balancer['connectionLimit'], - lb_type + lb.get('id'), + utils.lookup(lb, 'datacenter', 'longName'), + lb.get('address'), + lb.get('description'), + 'Yes' if lb.get('isPublic', 1) == 1 else 'No', + utils.clean_time(lb.get('createDate')), + lb.get('memberCount', 0), + lb.get('listenerCount', 0) + + ]) + return table - env.fout(table) diff --git a/SoftLayer/CLI/loadbal/ns_detail.py b/SoftLayer/CLI/loadbal/ns_detail.py new file mode 100644 index 000000000..976983b62 --- /dev/null +++ b/SoftLayer/CLI/loadbal/ns_detail.py @@ -0,0 +1,49 @@ +"""Get Netscaler details.""" +import click + +import SoftLayer +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 Netscaler details.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + lb = mgr.get_adc(identifier) + table = netscaler_table(lb) + env.fout(table) + + +def netscaler_table(lb): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['Id', lb.get('id')]) + table.add_row(['Type', lb.get('description')]) + table.add_row(['Name', lb.get('name')]) + table.add_row(['Location', utils.lookup(lb, 'datacenter', 'longName')]) + table.add_row(['Managment Ip', lb.get('managementIpAddress')]) + table.add_row(['Root Password', utils.lookup(lb, 'password', 'password')]) + table.add_row(['Primary Ip', lb.get('primaryIpAddress')]) + table.add_row(['License Expiration', utils.clean_time(lb.get('licenseExpirationDate'))]) + subnet_table = formatting.Table(['Id', 'Subnet', 'Type', 'Space']) + for subnet in lb.get('subnets', []): + subnet_table.add_row([ + subnet.get('id'), + "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')), + subnet.get('subnetType'), + subnet.get('addressSpace') + ]) + table.add_row(['Subnets', subnet_table]) + + vlan_table = formatting.Table(['Id', 'Number']) + for vlan in lb.get('networkVlans', []): + vlan_table.add_row([vlan.get('id'), vlan.get('vlanNumber')]) + table.add_row(['Vlans', vlan_table]) + + return table diff --git a/SoftLayer/CLI/loadbal/ns_list.py b/SoftLayer/CLI/loadbal/ns_list.py new file mode 100644 index 000000000..dd97a2f7e --- /dev/null +++ b/SoftLayer/CLI/loadbal/ns_list.py @@ -0,0 +1,46 @@ +"""List active Netscaler devices.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """List active Netscaler devices.""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + netscalers = mgr.get_adcs() + if netscalers: + adc_table = generate_netscaler_table(netscalers) + env.fout(adc_table) + else: + env.fout("No Netscalers") + + +def location_sort(x): + """Quick function that just returns the datacenter longName for sorting""" + return utils.lookup(x, 'datacenter', 'longName') + +def generate_netscaler_table(netscalers): + """Tales a list of SoftLayer_Network_Application_Delivery_Controller and makes a table""" + table = formatting.Table([ + 'Id', 'Location', 'Name', 'Description', 'IP Address', 'Management Ip', 'Bandwidth', 'Create Date' + ], title="Netscalers") + for adc in sorted(netscalers, key=location_sort): + table.add_row([ + adc.get('id'), + utils.lookup(adc, 'datacenter', 'longName'), + adc.get('name'), + adc.get('description'), + adc.get('primaryIpAddress'), + adc.get('managementIpAddress'), + adc.get('outboundPublicBandwidthUsage',0), + utils.clean_time(adc.get('createDate')) + ]) + return table + + diff --git a/SoftLayer/CLI/loadbal/routing_methods.py b/SoftLayer/CLI/loadbal/routing_methods.py deleted file mode 100644 index 3ecb9c3e9..000000000 --- a/SoftLayer/CLI/loadbal/routing_methods.py +++ /dev/null @@ -1,25 +0,0 @@ -"""List routing methods.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """List routing types.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - routing_methods = mgr.get_routing_methods() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_method in routing_methods: - table.add_row([routing_method['id'], routing_method['name']]) - - env.fout(table) diff --git a/SoftLayer/CLI/loadbal/routing_types.py b/SoftLayer/CLI/loadbal/routing_types.py deleted file mode 100644 index 88e8b8c3b..000000000 --- a/SoftLayer/CLI/loadbal/routing_types.py +++ /dev/null @@ -1,24 +0,0 @@ -"""List routing types.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """List routing types.""" - mgr = SoftLayer.LoadBalancerManager(env.client) - - routing_types = mgr.get_routing_types() - table = formatting.KeyValueTable(['ID', 'Name']) - table.align['ID'] = 'l' - table.align['Name'] = 'l' - table.sortby = 'ID' - for routing_type in routing_types: - table.add_row([routing_type['id'], routing_type['name']]) - env.fout(table) diff --git a/SoftLayer/CLI/loadbal/service_add.py b/SoftLayer/CLI/loadbal/service_add.py deleted file mode 100644 index 38d590392..000000000 --- a/SoftLayer/CLI/loadbal/service_add.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Adds a new load balancer service.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import loadbal - - -@click.command() -@click.argument('identifier') -@click.option('--enabled / --disabled', - required=True, - help="Create the service as enabled or disabled") -@click.option('--port', - required=True, - help="The port number for the service", - type=click.INT) -@click.option('--weight', - required=True, - type=click.INT, - help="The weight of the service") -@click.option('--healthcheck-type', - required=True, - help="The health check type") -@click.option('--ip-address', - required=True, - help="The IP address of the service") -@environment.pass_env -def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): - """Adds a new load balancer service.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - loadbal_id, group_id = loadbal.parse_id(identifier) - - # check if the IP is valid - ip_address_id = None - if ip_address: - ip_service = env.client['Network_Subnet_IpAddress'] - ip_record = ip_service.getByIpAddress(ip_address) - if len(ip_record) > 0: - ip_address_id = ip_record['id'] - - mgr.add_service(loadbal_id, - group_id, - ip_address_id=ip_address_id, - enabled=enabled, - port=port, - weight=weight, - hc_type=healthcheck_type) - env.fout('Load balancer service is being added!') diff --git a/SoftLayer/CLI/loadbal/service_delete.py b/SoftLayer/CLI/loadbal/service_delete.py deleted file mode 100644 index 130b2146e..000000000 --- a/SoftLayer/CLI/loadbal/service_delete.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Deletes an existing load balancer service.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Deletes an existing load balancer service.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - _, service_id = loadbal.parse_id(identifier) - - if not (env.skip_confirmations or - formatting.confirm("This action will cancel a service from your " - "load balancer. Continue?")): - raise exceptions.CLIAbort('Aborted.') - - mgr.delete_service(service_id) - env.fout('Load balancer service %s is being cancelled!' % service_id) diff --git a/SoftLayer/CLI/loadbal/service_edit.py b/SoftLayer/CLI/loadbal/service_edit.py deleted file mode 100644 index e8734cd50..000000000 --- a/SoftLayer/CLI/loadbal/service_edit.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Edit the properties of a service group.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@click.option('--enabled / --disabled', - default=None, - help="Enable or disable the service") -@click.option('--port', - help="Change the port number for the service", type=click.INT) -@click.option('--weight', - type=click.INT, - help="Change the weight of the service") -@click.option('--healthcheck-type', help="Change the health check type") -@click.option('--ip-address', help="Change the IP address of the service") -@environment.pass_env -def cli(env, identifier, enabled, port, weight, healthcheck_type, ip_address): - """Edit the properties of a service group.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - - loadbal_id, service_id = loadbal.parse_id(identifier) - - # check if any input is provided - if ((not any([ip_address, weight, port, healthcheck_type])) and - enabled is None): - raise exceptions.CLIAbort( - 'At least one property is required to be changed!') - - # check if the IP is valid - ip_address_id = None - if ip_address: - ip_service = env.client['Network_Subnet_IpAddress'] - ip_record = ip_service.getByIpAddress(ip_address) - ip_address_id = ip_record['id'] - - mgr.edit_service(loadbal_id, - service_id, - ip_address_id=ip_address_id, - enabled=enabled, - port=port, - weight=weight, - hc_type=healthcheck_type) - env.fout('Load balancer service %s is being modified!' % identifier) diff --git a/SoftLayer/CLI/loadbal/service_toggle.py b/SoftLayer/CLI/loadbal/service_toggle.py deleted file mode 100644 index 799b4333c..000000000 --- a/SoftLayer/CLI/loadbal/service_toggle.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Toggle the status of an existing load balancer service.""" -# :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 loadbal - - -@click.command() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Toggle the status of an existing load balancer service.""" - - mgr = SoftLayer.LoadBalancerManager(env.client) - _, service_id = loadbal.parse_id(identifier) - - if not (env.skip_confirmations or - formatting.confirm("This action will toggle the status on the " - "service. Continue?")): - raise exceptions.CLIAbort('Aborted.') - - mgr.toggle_service_status(service_id) - env.fout('Load balancer service %s status updated!' % identifier) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5524b6948..c87ad4296 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -167,22 +167,13 @@ ('ipsec:update', 'SoftLayer.CLI.vpn.ipsec.update:cli'), ('loadbal', 'SoftLayer.CLI.loadbal'), - ('loadbal:cancel', 'SoftLayer.CLI.loadbal.cancel:cli'), - ('loadbal:create', 'SoftLayer.CLI.loadbal.create:cli'), - ('loadbal:create-options', 'SoftLayer.CLI.loadbal.create_options:cli'), ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'), - ('loadbal:group-add', 'SoftLayer.CLI.loadbal.group_add:cli'), - ('loadbal:group-delete', 'SoftLayer.CLI.loadbal.group_delete:cli'), - ('loadbal:group-edit', 'SoftLayer.CLI.loadbal.group_edit:cli'), - ('loadbal:group-reset', 'SoftLayer.CLI.loadbal.group_reset:cli'), - ('loadbal:health-checks', 'SoftLayer.CLI.loadbal.health_checks:cli'), ('loadbal:list', 'SoftLayer.CLI.loadbal.list:cli'), - ('loadbal:routing-methods', 'SoftLayer.CLI.loadbal.routing_methods:cli'), - ('loadbal:routing-types', 'SoftLayer.CLI.loadbal.routing_types:cli'), - ('loadbal:service-add', 'SoftLayer.CLI.loadbal.service_add:cli'), - ('loadbal:service-delete', 'SoftLayer.CLI.loadbal.service_delete:cli'), - ('loadbal:service-edit', 'SoftLayer.CLI.loadbal.service_edit:cli'), - ('loadbal:service-toggle', 'SoftLayer.CLI.loadbal.service_toggle:cli'), + ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), + + ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), + ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), + ('metadata', 'SoftLayer.CLI.metadata:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 854c8c620..217e3b495 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -11,7 +11,7 @@ class LoadBalancerManager(utils.IdentifierMixin, object): """Manages SoftLayer load balancers. - See product information here: http://www.softlayer.com/load-balancing + See product information here: https://www.ibm.com/cloud/load-balancer :param SoftLayer.API.BaseClient client: the client instance @@ -21,8 +21,56 @@ def __init__(self, client): self.client = client self.account = self.client['Account'] self.prod_pkg = self.client['Product_Package'] - self.lb_svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_VirtualIpAddress'] + # Citrix Netscalers + self.adc = self.client['Network_Application_Delivery_Controller'] + # IBM CLoud LB + self.lbaas = self.client['Network_LBaaS_LoadBalancer'] + + def get_adcs(self, mask=None): + """Returns a list of all netscalers. + + :returns: SoftLayer_Network_Application_Delivery_Controller[]. + """ + if mask is None: + mask = 'mask[managementIpAddress,outboundPublicBandwidthUsage,primaryIpAddress,datacenter]' + return self.account.getApplicationDeliveryControllers(mask=mask) + + def get_adc(self, identifier, mask=None): + """Returns a netscaler object. + + :returns: SoftLayer_Network_Application_Delivery_Controller. + """ + if mask is None: + mask = "mask[networkVlans, password, managementIpAddress, primaryIpAddress, subnets, tagReferences, " \ + "licenseExpirationDate, datacenter]" + return self.adc.getObject(id=identifier, mask=mask) + + def get_lbaas(self, mask=None): + """Returns a list of IBM Cloud Loadbalancers + + :returns: SoftLayer_Network_LBaaS_LoadBalancer[] + """ + if mask is None: + mask = "mask[datacenter,listenerCount,memberCount]" + lb = self.lbaas.getAllObjects(mask=mask) + + return lb + + def get_lb(self, identifier, mask=None): + """Returns a IBM Cloud LoadBalancer + + :returns: SoftLayer_Network_LBaaS_LoadBalancer + """ + if mask is None: + mask = "mask[healthMonitors, l7Pools, listeners[defaultPool[healthMonitor, members, sessionAffinity],l7Policies], members, sslCiphers]" + + lb = self.lbaas.getObject(id=identifier, mask=mask) + health = self.lbaas.getLoadBalancerMemberHealth(lb.get('uuid')) + + lb['health'] = health + return lb + +# Old things below this line def get_lb_pkgs(self): """Retrieves the local load balancer packages. @@ -114,14 +162,7 @@ def add_local_lb(self, price_item_id, datacenter): } return self.client['Product_Order'].placeOrder(product_order) - def get_local_lbs(self): - """Returns a list of all local load balancers on the account. - - :returns: A list of all local load balancers on the current account. - """ - mask = 'loadBalancerHardware[datacenter],ipAddress' - return self.account.getAdcLoadBalancers(mask=mask) def get_local_lb(self, loadbal_id, **kwargs): """Returns a specified local load balancer given the id. diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index b70997842..21138e6ae 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -306,6 +306,8 @@ def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H: :param string in_format: Datetime format for strptime :param string out_format: Datetime format for strftime """ + if sltime is None: + return None try: clean = datetime.datetime.strptime(sltime, in_format) return clean.strftime(out_format) From c68eafa00e2241fdffc3c8499782e41a9739c9d4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 11 Jul 2019 16:49:58 -0500 Subject: [PATCH 0120/1582] #1047 manage health checks --- SoftLayer/CLI/loadbal/edit_members.py | 0 SoftLayer/CLI/loadbal/health.py | 56 +++++++++++++++++++++++++-- SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/load_balancer.py | 36 ++++++++++++++++- 4 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/CLI/loadbal/edit_members.py diff --git a/SoftLayer/CLI/loadbal/edit_members.py b/SoftLayer/CLI/loadbal/edit_members.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/loadbal/health.py b/SoftLayer/CLI/loadbal/health.py index d101b9b30..d487ee535 100644 --- a/SoftLayer/CLI/loadbal/health.py +++ b/SoftLayer/CLI/loadbal/health.py @@ -3,18 +3,66 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer import utils from pprint import pprint as pp @click.command() @click.argument('identifier') +@click.option('--uuid', required=True, help="Health check UUID to modify.") +@click.option('--interval', '-i', type=click.IntRange(2, 60), help="Seconds between checks. [2-60]") +@click.option('--retry', '-r', type=click.IntRange(1, 10), help="Number of times before marking as DOWN. [1-10]") +@click.option('--timeout', '-t', type=click.IntRange(1, 59), help="Seconds to wait for a connection. [1-59]") +@click.option('--url', '-u', help="Url path for HTTP/HTTPS checks.") @environment.pass_env -def cli(env, identifier): +def cli(env, identifier, uuid, interval, retry, timeout, url): """Manage LBaaS health checks.""" + + if not any([interval, retry, timeout, url]): + raise exceptions.ArgumentError("Specify either interval, retry, timeout, url") + + # map parameters to expected API names + template = {'healthMonitorUuid': uuid, 'interval': interval, 'maxRetries': retry, 'timeout': timeout, 'urlPath': url} + # Removes those empty values + clean_template = {k: v for k, v in template.items() if v is not None} + mgr = SoftLayer.LoadBalancerManager(env.client) + # Need to get the LBaaS uuid if it wasn't supplied + lb_uuid, lb_id = mgr.get_lbaas_uuid_id(identifier) + print("UUID: {}, ID: {}".format(lb_uuid, lb_id)) + + # Get the current health checks, and find the one we are updating. + mask = "mask[healthMonitors, listeners[uuid,defaultPool[healthMonitor]]]" + lbaas = mgr.get_lb(lb_id, mask=mask) + + check = {} + # Set the default values, because these all need to be set if we are not updating them. + for listener in lbaas.get('listeners', []): + if utils.lookup(listener, 'defaultPool', 'healthMonitor', 'uuid') == uuid: + check['backendProtocol'] = utils.lookup(listener, 'defaultPool', 'protocol') + check['backendPort'] = utils.lookup(listener, 'defaultPool', 'protocolPort') + check['healthMonitorUuid'] = uuid + check['interval'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'interval') + check['maxRetries'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'maxRetries') + check['timeout'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'timeout') + check['urlPath'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'urlPath') + + + if url and check['backendProtocol'] == 'TCP': + raise exceptions.ArgumentError('--url cannot be used with TCP checks') + + # Update existing check with supplied values + for key in clean_template.keys(): + check[key] = clean_template[key] + + result = mgr.updateLoadBalancerHealthMonitors(lb_uuid, [check]) + + if result: + click.secho('Health Check {} updated successfully'.format(uuid), fg='green') + else: + click.secho('ERROR: Failed to update {}'.format(uuid), fg='red') + - lb = mgr.get_lb(identifier) - table = lbaas_table(lb) - env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c87ad4296..b73a753d3 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -170,6 +170,7 @@ ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'), ('loadbal:list', 'SoftLayer.CLI.loadbal.list:cli'), ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), + ('loadbal:edit-members', 'SoftLayer.CLI.loadbal.edit_members:cli'), ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 217e3b495..2ff50f578 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -26,6 +26,8 @@ def __init__(self, client): # IBM CLoud LB self.lbaas = self.client['Network_LBaaS_LoadBalancer'] + + def get_adcs(self, mask=None): """Returns a list of all netscalers. @@ -62,7 +64,8 @@ def get_lb(self, identifier, mask=None): :returns: SoftLayer_Network_LBaaS_LoadBalancer """ if mask is None: - mask = "mask[healthMonitors, l7Pools, listeners[defaultPool[healthMonitor, members, sessionAffinity],l7Policies], members, sslCiphers]" + mask = "mask[healthMonitors, l7Pools, members, sslCiphers, " \ + "listeners[defaultPool[healthMonitor, members, sessionAffinity],l7Policies]]" lb = self.lbaas.getObject(id=identifier, mask=mask) health = self.lbaas.getLoadBalancerMemberHealth(lb.get('uuid')) @@ -70,6 +73,37 @@ def get_lb(self, identifier, mask=None): lb['health'] = health return lb + def get_lb_monitors(self, identifier, mask=None): + health = self.lbaas.getHealthMonitors(id=identifier) + return health + + def updateLoadBalancerHealthMonitors(self, uuid, checks): + """calls SoftLayer_Network_LBaaS_HealthMonitor::updateLoadBalancerHealthMonitors() + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_HealthMonitor/updateLoadBalancerHealthMonitors/ + https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration/ + :param uuid: loadBalancerUuid + :param checks list: SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration[] + """ + + # return self.lbaas.updateLoadBalancerHealthMonitors(uuid, checks) + return self.client.call('SoftLayer_Network_LBaaS_HealthMonitor', 'updateLoadBalancerHealthMonitors', + uuid, checks) + + def get_lbaas_uuid_id(self, identifier): + """Gets a LBaaS uuid, id. Since sometimes you need one or the other. + + :param identifier: either the LB Id, or UUID, this function will return both. + :return (uuid, id): + """ + if len(identifier) == 36: + lb = self.lbaas.getLoadBalancer(id=identifier, mask="mask[id,uuid]") + return identifier + else: + print("Finding out %s" % identifier) + lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") + return lb['uuid'], lb['id'] + # Old things below this line def get_lb_pkgs(self): From 10545a9e6c1c7c18528cad96e3fbe281545d4a1b Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sun, 14 Jul 2019 13:38:18 -0500 Subject: [PATCH 0121/1582] Add tests. Fix issues with create options. List transient flavors separately. --- SoftLayer/CLI/virt/create.py | 8 +- SoftLayer/CLI/virt/create_options.py | 57 ++++++++------ SoftLayer/CLI/virt/detail.py | 2 +- SoftLayer/CLI/virt/list.py | 6 +- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 12 +++ tests/CLI/modules/vs/vs_create_tests.py | 77 ++++++++++++++++++- tests/CLI/modules/vs/vs_tests.py | 7 +- tests/managers/vs/vs_tests.py | 4 +- 8 files changed, 138 insertions(+), 35 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index d39609a7e..96172ea8d 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -33,7 +33,7 @@ def _update_with_like_args(ctx, _, value): 'dedicated': like_details['dedicatedAccountHostOnlyFlag'], 'private': like_details['privateNetworkOnlyFlag'], 'placement_id': like_details.get('placementGroupId', None), - 'transient': like_details['transientGuestFlag'] or None, + 'transient': like_details.get('transientGuestFlag', None), } like_args['flavor'] = utils.lookup(like_details, @@ -144,7 +144,7 @@ def _parse_create_args(client, args): if args.get('transient') and not args.get('billing'): # No billing type specified and transient, so default to hourly - data['hourly'] = True + data['billing'] = 'hourly' if args.get('placementgroup'): resolver = SoftLayer.managers.PlacementManager(client).resolve_ids @@ -206,7 +206,7 @@ def _parse_create_args(client, args): help="Placement Group name or Id to order this guest on. See: slcli vs placementgroup list") @click.option('--ipv6', is_flag=True, help="Adds an IPv6 address to this guest") @click.option('--transient', is_flag=True, - help="Provisions the VS to be transient") + help="Create a transient virtual server") @environment.pass_env def cli(env, **args): """Order/create virtual servers.""" @@ -302,7 +302,7 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[--dedicated] not allowed with [--transient]') - if args['transient'] and not args['hourly']: + if args['transient'] and args['billing'] == 'monthly': raise exceptions.ArgumentError( '[--transient] not allowed with [--billing monthly]') diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index b50fde3d2..601c0f3ac 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -12,7 +12,7 @@ from SoftLayer import utils -@click.command() +@click.command(short_help="Get options to use for creating virtual servers.") @environment.pass_env def cli(env): """Virtual server order options.""" @@ -32,27 +32,7 @@ def cli(env): table.add_row(['datacenter', formatting.listing(datacenters, separator='\n')]) - def _add_flavor_rows(flavor_key, flavor_label, flavor_options): - flavors = [] - - for flavor_option in flavor_options: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - if not flavor_key_name.startswith(flavor_key): - continue - - flavors.append(flavor_key_name) - - if len(flavors) > 0: - table.add_row(['flavors (%s)' % flavor_label, - formatting.listing(flavors, separator='\n')]) - - if result.get('flavors', None): - _add_flavor_rows('B1', 'balanced', result['flavors']) - _add_flavor_rows('BL1', 'balanced local - hdd', result['flavors']) - _add_flavor_rows('BL2', 'balanced local - ssd', result['flavors']) - _add_flavor_rows('C1', 'compute', result['flavors']) - _add_flavor_rows('M1', 'memory', result['flavors']) - _add_flavor_rows('AC', 'GPU', result['flavors']) + _add_flavors_to_table(result, table) # CPUs standard_cpus = [int(x['template']['startCpus']) for x in result['processors'] @@ -167,3 +147,36 @@ def add_block_rows(disks, name): formatting.listing(ded_host_speeds, separator=',')]) env.fout(table) + + +def _add_flavors_to_table(result, table): + grouping = { + 'balanced': {'key_starts_with': 'B1', 'flavors': []}, + 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, + 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, + 'compute': {'key_starts_with': 'C1', 'flavors': []}, + 'memory': {'key_starts_with': 'M1', 'flavors': []}, + 'GPU': {'key_starts_with': 'AC', 'flavors': []}, + 'transient': {'transient': True, 'flavors': []}, + } + + if result.get('flavors', None) is None: + return + + for flavor_option in result['flavors']: + flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') + + for name, group in grouping.items(): + if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: + if utils.lookup(group, 'transient') is True: + group['flavors'].append(flavor_key_name) + break + + elif utils.lookup(group, 'key_starts_with') is not None \ + and flavor_key_name.startswith(group['key_starts_with']): + group['flavors'].append(flavor_key_name) + break + + for name, group in grouping.items(): + if len(group['flavors']) > 0: + table.add_row(['flavors (%s)' % name, formatting.listing(group['flavors'], separator='\n')]) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 2c9771b21..53ae7e04d 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -61,7 +61,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) table.add_row(['private_cpu', result['dedicatedAccountHostOnlyFlag']]) - table.add_row(['transient', result['transientGuestFlag']]) + table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 3975ad333..6bf9e6bb6 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -42,7 +42,7 @@ ] -@click.command() +@click.command(short_help="List virtual servers.") @click.option('--cpu', '-c', help='Number of CPU cores', type=click.INT) @click.option('--domain', '-D', help='Domain portion of the FQDN') @click.option('--datacenter', '-d', help='Datacenter shortname') @@ -51,6 +51,7 @@ @click.option('--network', '-n', help='Network port speed in Mbps') @click.option('--hourly', is_flag=True, help='Show only hourly instances') @click.option('--monthly', is_flag=True, help='Show only monthly instances') +@click.option('--transient', help='Filter by transient instances', type=click.BOOL) @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -68,7 +69,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit): + hourly, monthly, tag, columns, limit, transient): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -80,6 +81,7 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, memory=memory, datacenter=datacenter, nic_speed=network, + transient=transient, tags=tag, mask=columns.mask(), limit=limit) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index aaae79b73..270ecf2ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -49,6 +49,7 @@ 'vlanNumber': 23, 'id': 1}], 'dedicatedHost': {'id': 37401}, + 'transientGuestFlag': False, 'operatingSystem': { 'passwords': [{'username': 'user', 'password': 'pass'}], 'softwareLicense': { @@ -75,6 +76,17 @@ } } }, + { + 'flavor': { + 'keyName': 'B1_1X2X25_TRANSIENT' + }, + 'template': { + 'supplementalCreateObjectOptions': { + 'flavorKeyName': 'B1_1X2X25_TRANSIENT' + }, + 'transientGuestFlag': True + } + }, { 'flavor': { 'keyName': 'B1_1X2X100' diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 91946471a..38adf3ea7 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -485,6 +485,49 @@ def test_create_like_flavor(self, confirm_mock): 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_like_transient(self, confirm_mock): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') + mock.return_value = { + 'hostname': 'vs-test-like', + 'domain': 'test.sftlyr.ws', + 'datacenter': {'name': 'dal05'}, + 'networkComponents': [{'maxSpeed': 100}], + 'dedicatedAccountHostOnlyFlag': False, + 'privateNetworkOnlyFlag': False, + 'billingItem': {'orderItem': {'preset': {'keyName': 'B1_1X2X25'}}}, + 'operatingSystem': {'softwareLicense': { + 'softwareDescription': {'referenceCode': 'UBUNTU_LATEST'} + }}, + 'hourlyBillingFlag': True, + 'localDiskFlag': False, + 'transientGuestFlag': True, + 'userData': {} + } + + confirm_mock.return_value = True + result = self.run_command(['vs', 'create', '--like=123']) + + self.assert_no_fail(result) + self.assertIn('"guid": "1a2b3c-1701"', result.output) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + + args = ({'datacenter': {'name': 'dal05'}, + 'domain': 'test.sftlyr.ws', + 'hourlyBillingFlag': True, + 'hostname': 'vs-test-like', + 'startCpus': None, + 'maxMemory': None, + 'localDiskFlag': None, + 'transientGuestFlag': True, + 'supplementalCreateObjectOptions': { + 'bootMode': None, + 'flavorKeyName': 'B1_1X2X25'}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'networkComponents': [{'maxSpeed': 100}]},) + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_vs_test(self, confirm_mock): confirm_mock.return_value = True @@ -511,9 +554,39 @@ def test_create_vs_bad_memory(self): result = self.run_command(['vs', 'create', '--hostname', 'TEST', '--domain', 'TESTING', '--cpu', '1', '--memory', '2034MB', '--flavor', - 'UBUNTU', '--datacenter', 'TEST00']) + 'B1_2X8X25', '--datacenter', 'TEST00']) - self.assertEqual(result.exit_code, 2) + self.assertEqual(2, result.exit_code) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_vs_transient(self, confirm_mock): + confirm_mock.return_value = True + + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--os', 'UBUNTU_LATEST']) + + self.assert_no_fail(result) + self.assertEqual(0, result.exit_code) + + def test_create_vs_bad_transient_monthly(self): + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--billing', 'monthly', + '--os', 'UBUNTU_LATEST']) + + self.assertEqual(2, result.exit_code) + + def test_create_vs_bad_transient_dedicated(self): + result = self.run_command(['vs', 'create', '--hostname', 'TEST', + '--domain', 'TESTING', '--flavor', + 'B1_2X8X25', '--datacenter', 'TEST00', + '--transient', '--dedicated', + '--os', 'UBUNTU_LATEST']) + + self.assertEqual(2, result.exit_code) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_with_ipv6(self, confirm_mock): diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 16016d450..203230913 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -268,8 +268,7 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'cpus (dedicated host)': [4, 56], + self.assertEqual({'cpus (dedicated host)': [4, 56], 'cpus (dedicated)': [1], 'cpus (standard)': [1, 2, 3, 4], 'datacenter': ['ams01', 'dal05'], @@ -279,6 +278,7 @@ def test_create_options(self): 'flavors (compute)': ['C1_1X2X25'], 'flavors (memory)': ['M1_1X2X100'], 'flavors (GPU)': ['AC1_1X2X100', 'ACL1_1X2X100'], + 'flavors (transient)': ['B1_1X2X25_TRANSIENT'], 'local disk(0)': ['25', '100'], 'memory': [1024, 2048, 3072, 4096], 'memory (dedicated host)': [8192, 65536], @@ -286,7 +286,8 @@ def test_create_options(self): 'nic (dedicated host)': ['1000'], 'os (CENTOS)': 'CENTOS_6_64', 'os (DEBIAN)': 'DEBIAN_7_64', - 'os (UBUNTU)': 'UBUNTU_12_64'}) + 'os (UBUNTU)': 'UBUNTU_12_64'}, + json.loads(result.output)) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 2192e642b..2816f8b06 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -60,6 +60,7 @@ def test_list_instances_with_filters(self): nic_speed=100, public_ip='1.2.3.4', private_ip='4.3.2.1', + transient=False, ) _filter = { @@ -78,7 +79,8 @@ def test_list_instances_with_filters(self): 'hostname': {'operation': '_= hostname'}, 'networkComponents': {'maxSpeed': {'operation': 100}}, 'primaryIpAddress': {'operation': '_= 1.2.3.4'}, - 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'} + 'primaryBackendIpAddress': {'operation': '_= 4.3.2.1'}, + 'transientGuestFlag': {'operation': False}, } } self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', From 36e210b2369281caeb3537a9aee2da8a64b4cbf2 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sun, 14 Jul 2019 13:44:20 -0500 Subject: [PATCH 0122/1582] Fix spacing. --- tests/CLI/modules/vs/vs_create_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 38adf3ea7..761778db6 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -485,7 +485,6 @@ def test_create_like_flavor(self, confirm_mock): 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_like_transient(self, confirm_mock): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') From db50af5a35eebe31dc05738ab52c91f62666389e Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sun, 14 Jul 2019 14:22:52 -0500 Subject: [PATCH 0123/1582] Reorder option to catch transient and dedicated first. --- SoftLayer/CLI/virt/create.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 96172ea8d..70430bc8f 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -142,10 +142,6 @@ def _parse_create_args(client, args): if args.get('host_id'): data['host_id'] = args['host_id'] - if args.get('transient') and not args.get('billing'): - # No billing type specified and transient, so default to hourly - data['billing'] = 'hourly' - if args.get('placementgroup'): resolver = SoftLayer.managers.PlacementManager(client).resolve_ids data['placement_id'] = helpers.resolve_id(resolver, args.get('placementgroup'), 'PlacementGroup') @@ -290,6 +286,10 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[-m | --memory] not allowed with [-f | --flavor]') + if all([args['dedicated'], args['transient']]): + raise exceptions.ArgumentError( + '[--dedicated] not allowed with [--transient]') + if all([args['dedicated'], args['flavor']]): raise exceptions.ArgumentError( '[-d | --dedicated] not allowed with [-f | --flavor]') @@ -298,10 +298,6 @@ def _validate_args(env, args): raise exceptions.ArgumentError( '[-h | --host-id] not allowed with [-f | --flavor]') - if all([args['dedicated'], args['transient']]): - raise exceptions.ArgumentError( - '[--dedicated] not allowed with [--transient]') - if args['transient'] and args['billing'] == 'monthly': raise exceptions.ArgumentError( '[--transient] not allowed with [--billing monthly]') From 1b907bff72d08a3b7f4af429a26a2871f298fdfb Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 15 Jul 2019 15:36:56 -0500 Subject: [PATCH 0124/1582] lbaas member-add and member-del commands --- SoftLayer/CLI/loadbal/detail.py | 9 ++-- SoftLayer/CLI/loadbal/edit_members.py | 0 SoftLayer/CLI/loadbal/list.py | 1 + SoftLayer/CLI/loadbal/members.py | 62 +++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 3 +- SoftLayer/managers/load_balancer.py | 28 ++++++++++-- 6 files changed, 96 insertions(+), 7 deletions(-) delete mode 100644 SoftLayer/CLI/loadbal/edit_members.py create mode 100644 SoftLayer/CLI/loadbal/members.py diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 6f29e7655..14726eaa0 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -13,9 +13,12 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - - lb = mgr.get_lb(identifier) - pp(lb) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + lb = mgr.get_lb(lbid) + # pp(lb) + if lb.get('previousErrorText'): + print("THERE WAS AN ERROR") + print(lb.get('previousErrorText')) table = lbaas_table(lb) env.fout(table) diff --git a/SoftLayer/CLI/loadbal/edit_members.py b/SoftLayer/CLI/loadbal/edit_members.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py index e8394170c..228a98ec6 100644 --- a/SoftLayer/CLI/loadbal/list.py +++ b/SoftLayer/CLI/loadbal/list.py @@ -37,6 +37,7 @@ def generate_lbaas_table(lbaas): table.align['Description'] = 'l' table.align['Location'] = 'l' for lb in sorted(lbaas,key=location_sort): + print("PUBLIC: {}".format(lb.get('isPublic'))) table.add_row([ lb.get('id'), utils.lookup(lb, 'datacenter', 'longName'), diff --git a/SoftLayer/CLI/loadbal/members.py b/SoftLayer/CLI/loadbal/members.py new file mode 100644 index 000000000..0f0ffe228 --- /dev/null +++ b/SoftLayer/CLI/loadbal/members.py @@ -0,0 +1,62 @@ +"""Manage LBaaS members.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment, formatting, helpers +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer import utils +from pprint import pprint as pp + +@click.command() +@click.argument('identifier') +@click.option('--member', '-m', required=True, help="Member UUID") +@environment.pass_env +def remove(env, identifier, member): + """Remove a LBaaS member. + + Member UUID can be found from `slcli lb detail`. + """ + + mgr = SoftLayer.LoadBalancerManager(env.client) + + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + # Get a member ID to remove + + try: + result = mgr.delete_lb_member(uuid, member) + click.secho("Member {} removed".format(member), fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') + + +@click.command() +@click.argument('identifier') +@click.option('--private/--public', default=True, required=True, help="Private or public IP of the new member.") +@click.option('--member', '-m', required=True, help="Member IP address.") +@click.option('--weight', '-w', default=50, type=int, help="Weight of this member.") +@environment.pass_env +def add(env, identifier, private, member, weight): + """Add a new LBaaS members.""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + # Get a server ID to add + to_add = {"weight": weight} + if private: + to_add['privateIpAddress'] = member + else: + to_add['publicIpAddress'] = member + + try: + result = mgr.add_lb_member(uuid, to_add) + click.secho("Member {} added".format(member), fg='green') + except SoftLayerAPIError as e: + if 'publicIpAddress must be a string' in e.faultString: + click.secho("This LB requires a Public IP address for its members and none was supplied", fg='red') + elif 'privateIpAddress must be a string' in e.faultString: + click.secho("This LB requires a Private IP address for its members and none was supplied", fg='red') + click.secho("ERROR: {}".format(e.faultString), fg='red') + + + + diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b73a753d3..b95adcd12 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -170,7 +170,8 @@ ('loadbal:detail', 'SoftLayer.CLI.loadbal.detail:cli'), ('loadbal:list', 'SoftLayer.CLI.loadbal.list:cli'), ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), - ('loadbal:edit-members', 'SoftLayer.CLI.loadbal.edit_members:cli'), + ('loadbal:member-add', 'SoftLayer.CLI.loadbal.members:add'), + ('loadbal:member-del', 'SoftLayer.CLI.loadbal.members:remove'), ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 2ff50f578..a042e79f7 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -97,13 +97,35 @@ def get_lbaas_uuid_id(self, identifier): :return (uuid, id): """ if len(identifier) == 36: - lb = self.lbaas.getLoadBalancer(id=identifier, mask="mask[id,uuid]") - return identifier + lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") else: - print("Finding out %s" % identifier) lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") return lb['uuid'], lb['id'] + def delete_lb_member(self, identifier, member_id): + """Removes a member from a LBaaS instance + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_Member/deleteLoadBalancerMembers/ + :param identifier: UUID of the LBaaS instance + :param member_id: Member UUID to remove. + """ + result = self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', + identifier, [member_id]) + return result + + def add_lb_member(self, identifier, member_id): + """Removes a member from a LBaaS instance + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_Member/deleteLoadBalancerMembers/ + :param identifier: UUID of the LBaaS instance + :param member_id: Member UUID to remove. + """ + + result = self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', + identifier, [member_id]) + + return result + # Old things below this line def get_lb_pkgs(self): From feab68df0bb3d6a69848d0cd1c648412b366adb0 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 17 Jul 2019 15:50:02 -0500 Subject: [PATCH 0125/1582] LB pool management commands --- SoftLayer/CLI/loadbal/pools.py | 119 ++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 3 + SoftLayer/managers/load_balancer.py | 26 ++++++ 3 files changed, 148 insertions(+) create mode 100644 SoftLayer/CLI/loadbal/pools.py diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py new file mode 100644 index 000000000..8accc5e3a --- /dev/null +++ b/SoftLayer/CLI/loadbal/pools.py @@ -0,0 +1,119 @@ +"""Manage LBaaS Pools/Listeners.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment, formatting, helpers +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer import utils +from pprint import pprint as pp + + +def sticky_option(ctx, param, value): + if value: + return 'SOURCE_IP' + return None + +@click.command() +@click.argument('identifier') +@click.option('--frontProtocol', '-P', default='HTTP', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), show_default=True, + help="Protocol type to use for incoming connections") +@click.option('--backProtocol', '-p', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), + help="Protocol type to use when connecting to backend servers. Defaults to whatever --frontProtocol is.") +@click.option('--frontPort', '-f', required=True, type=int, help="Internet side port") +@click.option('--backPort', '-b', required=True, type=int, help="Private side port") +@click.option('--method', '-m', default='ROUNDROBIN', show_default=True, help="Balancing Method", + type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) +@click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") +@click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") +@environment.pass_env +def add(env, identifier, **args): + """Adds a listener to the identifier LB""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + + new_listener = { + 'backendPort': args.get('backport'), + 'backendProtocol': args.get('backprotocol') if args.get('backprotocol') else args.get('frontprotocol'), + 'frontendPort': args.get('frontport'), + 'frontendProtocol': args.get('frontprotocol'), + 'loadBalancingMethod': args.get('method'), + 'maxConn': args.get('connections', None), + 'sessionType': args.get('sticky'), + 'tlsCertificateId': None + } + + try: + result = mgr.add_lb_listener(uuid, new_listener) + click.secho("Success", fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') + + +@click.command() +@click.argument('identifier') +@click.argument('listener') +@click.option('--frontProtocol', '-P', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), + help="Protocol type to use for incoming connections") +@click.option('--backProtocol', '-p', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), + help="Protocol type to use when connecting to backend servers. Defaults to whatever --frontProtocol is.") +@click.option('--frontPort', '-f', type=int, help="Internet side port") +@click.option('--backPort', '-b', type=int, help="Private side port") +@click.option('--method', '-m', help="Balancing Method", + type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) +@click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") +@click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") +@environment.pass_env +def edit(env, identifier, listener, **args): + """Updates a listener's configuration. + + LISTENER should be a UUID, and can be found from `slcli lb detail ` + """ + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + + + new_listener = { + 'listenerUuid': listener + } + + arg_to_option = { + 'frontprotocol': 'frontendProtocol', + 'backprotocol': 'backendProtocol', + 'frontport': 'frontendPort', + 'backport': 'backendPort', + 'method': 'loadBalancingMethod', + 'connections': 'maxConn', + 'sticky': 'sessionType', + 'sslcert': 'tlsCertificateId' + } + + for arg in args.keys(): + if args[arg]: + new_listener[arg_to_option[arg]] = args[arg] + + try: + result = mgr.add_lb_listener(uuid, new_listener) + click.secho("Success", fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') + + +@click.command() +@click.argument('identifier') +@click.argument('listener') +@environment.pass_env +def delete(env, identifier, listener): + """Removes the listener from identified LBaaS instance + + LISTENER should be a UUID, and can be found from `slcli lb detail ` + """ + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + try: + result = mgr.remove_lb_listener(uuid, listener) + click.secho("Success", fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b95adcd12..fa8c8e77d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -172,6 +172,9 @@ ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), ('loadbal:member-add', 'SoftLayer.CLI.loadbal.members:add'), ('loadbal:member-del', 'SoftLayer.CLI.loadbal.members:remove'), + ('loadbal:pool-add', 'SoftLayer.CLI.loadbal.pools:add'), + ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), + ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index a042e79f7..3777f0c96 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -126,6 +126,32 @@ def add_lb_member(self, identifier, member_id): return result + def add_lb_listener(self, identifier, listener): + """Adds or update a listener to a LBaaS instance + + When using this to update a listener, just include the 'listenerUuid' in the listener object + See the following for listener configuration options + https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancerProtocolConfiguration/ + + :param identifier: UUID of the LBaaS instance + :param listener: Object with all listener configurations + """ + + result = self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', + identifier, [listener]) + return result + + def remove_lb_listener(self, identifier, listener): + """Removes a listener to a LBaaS instance + + :param identifier: UUID of the LBaaS instance + :param listener: UUID of the Listner to be removed. + """ + + result = self.client.call('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', + identifier, [listener]) + return result + # Old things below this line def get_lb_pkgs(self): From 6132b58cfdfba0f37909a7f522cf0cd965e436bf Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 18 Jul 2019 17:13:37 -0500 Subject: [PATCH 0126/1582] work around l7 pools --- SoftLayer/CLI/loadbal/pools.py | 85 ++++++++++++++++++++++++++++- SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/load_balancer.py | 54 ++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index 8accc5e3a..7ada89118 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -2,7 +2,7 @@ import click import SoftLayer -from SoftLayer.CLI import environment, formatting, helpers +from SoftLayer.CLI import environment, formatting, helpers, exceptions from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils from pprint import pprint as pp @@ -25,6 +25,7 @@ def sticky_option(ctx, param, value): type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") +@click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env def add(env, identifier, **args): """Adds a listener to the identifier LB""" @@ -40,7 +41,7 @@ def add(env, identifier, **args): 'loadBalancingMethod': args.get('method'), 'maxConn': args.get('connections', None), 'sessionType': args.get('sticky'), - 'tlsCertificateId': None + 'tlsCertificateId': args.get('sslcert') } try: @@ -63,6 +64,7 @@ def add(env, identifier, **args): type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") +@click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env def edit(env, identifier, listener, **args): """Updates a listener's configuration. @@ -116,4 +118,81 @@ def delete(env, identifier, listener): result = mgr.remove_lb_listener(uuid, listener) click.secho("Success", fg='green') except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') \ No newline at end of file + click.secho("ERROR: {}".format(e.faultString), fg='red') + +def parse_server(ctx, param, values): + """Splits out the IP, Port, Weight from the --server argument for l7pools""" + servers = [] + for server in values: + splitout = server.split(':') + if len(splitout) != 3: + raise exceptions.ArgumentError("--server needs a port and a weight. {} improperly formatted".format(server)) + server = { + 'address': splitout[0], + 'port': splitout[1], + 'weight': splitout[2] + } + servers.append(server) + + return servers + +@click.command() +@click.argument('identifier') +# https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ +@click.option('--name', '-n', required=True, help="Name for this L7 pool.") +@click.option('--method', '-m', help="Balancing Method.", default='ROUNDROBIN', show_default=True, + type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) +@click.option('--protocol', '-P', type=click.Choice(['HTTP', 'HTTPS']), default='HTTP', + show_default=True, help="Protocol type to use for incoming connections") +# https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Member/ +@helpers.multi_option('--server', '-S', callback=parse_server, required=True, + help="Backend servers that are part of this pool. Format is colon deliminated. " \ + "BACKEND_IP:PORT:WEIGHT. eg. 10.0.0.1:80:50") +# https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7HealthMonitor/ +@click.option('--healthPath', default='/', show_default=True, help="Health check path.") +@click.option('--healthInterval', default=5, type=int, show_default=True, help="Health check interval between checks.") +@click.option('--healthRetry', default=2, type=int, show_default=True, + help="Health check number of times before marking as DOWN.") +@click.option('--healthTimeout', default=2, type=int, show_default=True, help="Health check timeout.") +# https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7SessionAffinity/ +@click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") +@environment.pass_env +def l7pool_add(env, identifier, **args): + """Adds a new l7 pool + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_L7Pool/createL7Pool/ + + -S is in : deliminated format to make grouping IP:port:weight a bit easier. + """ + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + + pool_main = { + 'name': args.get('name'), + 'loadBalancingAlgorithm': args.get('method'), + 'protocol': args.get('protocol') + } + + pool_members = [member for member in args.get('server')] + + pool_health = { + 'interval': args.get('healthinterval'), + 'timeout': args.get('healthtimeout'), + 'maxRetries': args.get('healthretry'), + 'urlPath': args.get('healthpath') + } + + pool_sticky = { + 'type': args.get('sticky') + } + + try: + result = mgr.add_lb_l7_pool(uuid, pool_main, pool_members, pool_health, None) + pp(result) + click.secho("Success", fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') + + + diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fa8c8e77d..d637fa866 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -175,6 +175,7 @@ ('loadbal:pool-add', 'SoftLayer.CLI.loadbal.pools:add'), ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), + ('loadbal:l7pool-add', 'SoftLayer.CLI.loadbal.pools:l7pool_add'), ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 3777f0c96..6592d0433 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -141,6 +141,60 @@ def add_lb_listener(self, identifier, listener): identifier, [listener]) return result + def add_lb_l7_pool(self, identifier, pool, members, health, session): + """Creates a new l7 pool for a LBaaS instance + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_L7Pool/createL7Pool/ + https://cloud.ibm.com/docs/infrastructure/loadbalancer-service?topic=loadbalancer-service-api-reference + + :param identifier: UUID of the LBaaS instance + :param pool SoftLayer_Network_LBaaS_L7Pool: Description of the pool + :param members SoftLayer_Network_LBaaS_L7Member[]: Array of servers with their address, port, weight + :param monitor SoftLayer_Network_LBaaS_L7HealthMonitor: A health monitor + :param session SoftLayer_Network_LBaaS_L7SessionAffinity: Weather to use affinity + """ + + l7Members = [ + { + 'address': '10.131.11.60', + 'port': 82, + 'weight': 10 + }, + { + 'address': '10.131.11.46', + 'port': 83, + 'weight': 11 + } + ] + + l7Pool = { + 'name': 'image112_pool', + 'protocol': 'HTTP', # only supports HTTP + 'loadBalancingAlgorithm': 'ROUNDROBIN' + } + + l7HealthMonitor = { + 'interval': 10, + 'timeout': 5, + 'maxRetries': 3, + 'urlPath': '/' + } + + # Layer 7 session affinity to be added. Only supports SOURCE_IP as of now + l7SessionAffinity = { + 'type': 'SOURCE_IP' + } + + # result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', + # identifier, pool, members, health, session) + result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', + identifier, l7Pool, l7Members, l7HealthMonitor, l7SessionAffinity) + + + # string, member, monitor, affinity + + return result + def remove_lb_listener(self, identifier, listener): """Removes a listener to a LBaaS instance From f378a8ea9a5a257e1a09275dbf99ef666700f84d Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 29 Jul 2019 18:10:54 -0500 Subject: [PATCH 0127/1582] loadbalancer order options command --- SoftLayer/CLI/loadbal/detail.py | 3 +- SoftLayer/CLI/loadbal/order.py | 107 ++++++++++++++++++++++++++++ SoftLayer/CLI/loadbal/pools.py | 21 ++++-- SoftLayer/CLI/routes.py | 5 ++ SoftLayer/managers/load_balancer.py | 91 ++++++++++++++--------- 5 files changed, 186 insertions(+), 41 deletions(-) create mode 100644 SoftLayer/CLI/loadbal/order.py diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 14726eaa0..545c19cde 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -52,9 +52,10 @@ def lbaas_table(lb): table.add_row(['Checks', hp_table]) # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ - l7_table = formatting.Table(['UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active' ]) + l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active' ]) for l7 in lb.get('l7Pools', []): l7_table.add_row([ + l7.get('id'), l7.get('uuid'), l7.get('loadBalancingAlgorithm'), l7.get('name'), diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py new file mode 100644 index 000000000..71440063c --- /dev/null +++ b/SoftLayer/CLI/loadbal/order.py @@ -0,0 +1,107 @@ +"""Order and Cancel LBaaS instances.""" +import click + +import SoftLayer +from SoftLayer.CLI import environment, formatting, helpers, exceptions +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer import utils +from pprint import pprint as pp + + + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def order(env, identifier): + """Creates a LB""" + print("Nothing yet") + mgr = SoftLayer.LoadBalancerManager(env.client) + package_name = 'Load Balancer As A Service (LBaaS)' + location = 'MEXICO' + name = 'My-LBaaS-name' + description = 'A description sample' + + # Set False for private network + is_public = True + + protocols = [ + { + "backendPort": 80, + "backendProtocol": "HTTP", + "frontendPort": 8080, + "frontendProtocol": "HTTP", + "loadBalancingMethod": "ROUNDROBIN", # ROUNDROBIN, LEASTCONNECTION, WEIGHTED_RR + "maxConn": 1000 + } + ] + + # remove verify=True to place the order + receipt = lbaas.order_lbaas(package_name, location, name, description, + protocols, public=is_public, verify=True) + + +@click.command() +@click.option('--datacenter', '-d', help="Show only selected datacenter, use shortname (dal13) format.") +@environment.pass_env +def order_options(env, datacenter): + """Prints options for order a LBaaS""" + print("Prints options for ordering") + mgr = SoftLayer.LoadBalancerManager(env.client) + net_mgr = SoftLayer.NetworkManager(env.client) + package = mgr.lbaas_order_options() + + tables = [] + for region in package['regions']: + dc_name = utils.lookup(region, 'location', 'location', 'name') + + # Skip locations if they are not the one requested. + if datacenter and dc_name != datacenter: + continue + this_table = formatting.Table( + ['Prices', 'Private Subnets'], + title="{}: {}".format(region['keyname'], region['description']) + ) + + l_groups = [] + for group in region['location']['location']['groups']: + l_groups.append(group.get('id')) + + # Price lookups + prices = [] + price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + for item in package['items']: + i_price = {'keyName': item['keyName']} + for price in item.get('prices', []): + if not price.get('locationGroupId'): + i_price['default_price'] = price.get('hourlyRecurringFee') + elif price.get('locationGroupId') in l_groups: + i_price['region_price'] = price.get('hourlyRecurringFee') + prices.append(i_price) + for price in prices: + if price.get('region_price'): + price_table.add_row([price.get('keyName'), price.get('region_price')]) + else: + price_table.add_row([price.get('keyName'), price.get('default_price')]) + + # Vlan/Subnet Lookups + mask = "mask[networkVlan,podName,addressSpace]" + subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) + subnet_table = formatting.KeyValueTable(['Subnet', 'Vlan']) + + for subnet in subnets: + # Only show these types, easier to filter here than in an API call. + if subnet.get('subnetType') != 'PRIMARY' and subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + continue + space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) + vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) + subnet_table.add_row([space, vlan]) + this_table.add_row([price_table, subnet_table]) + + env.fout(this_table) + + +@click.command() +@environment.pass_env +def cancel(env, identifier, **args): + print("Nothing yet") \ No newline at end of file diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index 7ada89118..b9a11d929 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -160,9 +160,7 @@ def parse_server(ctx, param, values): def l7pool_add(env, identifier, **args): """Adds a new l7 pool - https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_L7Pool/createL7Pool/ - - -S is in : deliminated format to make grouping IP:port:weight a bit easier. + -S is in colon deliminated format to make grouping IP:port:weight a bit easier. """ mgr = SoftLayer.LoadBalancerManager(env.client) @@ -188,11 +186,24 @@ def l7pool_add(env, identifier, **args): } try: - result = mgr.add_lb_l7_pool(uuid, pool_main, pool_members, pool_health, None) - pp(result) + result = mgr.add_lb_l7_pool(uuid, pool_main, pool_members, pool_health, pool_sticky) click.secho("Success", fg='green') except SoftLayerAPIError as e: click.secho("ERROR: {}".format(e.faultString), fg='red') +@click.command() +@click.argument('identifier') +@environment.pass_env +def l7pool_del(env, identifier): + """Deletes the identified pool + + Identifier is L7Pool Id. NOT the UUID + """ + mgr = SoftLayer.LoadBalancerManager(env.client) + try: + result = mgr.del_lb_l7_pool(identifier) + click.secho("Success", fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d637fa866..fb33ee9e2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -176,6 +176,11 @@ ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), ('loadbal:l7pool-add', 'SoftLayer.CLI.loadbal.pools:l7pool_add'), + ('loadbal:l7pool-del', 'SoftLayer.CLI.loadbal.pools:l7pool_del'), + ('loadbal:order', 'SoftLayer.CLI.loadbal.order:order'), + ('loadbal:order-options', 'SoftLayer.CLI.loadbal.order:order_options'), + ('loadbal:cancel', 'SoftLayer.CLI.loadbal.order:cancel'), + ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 6592d0433..0430a179d 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -25,6 +25,7 @@ def __init__(self, client): self.adc = self.client['Network_Application_Delivery_Controller'] # IBM CLoud LB self.lbaas = self.client['Network_LBaaS_LoadBalancer'] + self.package_keyname = 'LBAAS' @@ -154,47 +155,20 @@ def add_lb_l7_pool(self, identifier, pool, members, health, session): :param session SoftLayer_Network_LBaaS_L7SessionAffinity: Weather to use affinity """ - l7Members = [ - { - 'address': '10.131.11.60', - 'port': 82, - 'weight': 10 - }, - { - 'address': '10.131.11.46', - 'port': 83, - 'weight': 11 - } - ] - - l7Pool = { - 'name': 'image112_pool', - 'protocol': 'HTTP', # only supports HTTP - 'loadBalancingAlgorithm': 'ROUNDROBIN' - } - - l7HealthMonitor = { - 'interval': 10, - 'timeout': 5, - 'maxRetries': 3, - 'urlPath': '/' - } - - # Layer 7 session affinity to be added. Only supports SOURCE_IP as of now - l7SessionAffinity = { - 'type': 'SOURCE_IP' - } - - # result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', - # identifier, pool, members, health, session) result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', - identifier, l7Pool, l7Members, l7HealthMonitor, l7SessionAffinity) + identifier, pool, members, health, session) + return result - # string, member, monitor, affinity + def del_lb_l7_pool(self, identifier): + """Deletes a l7 pool + :param identifier: Id of the L7Pool + """ + result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'deleteObject', id=identifier) return result + def remove_lb_listener(self, identifier, listener): """Removes a listener to a LBaaS instance @@ -206,6 +180,53 @@ def remove_lb_listener(self, identifier, listener): identifier, [listener]) return result + def order_lbaas(self, datacenter, name, desc, protocols, subnet_id=None, public=False, verify=False): + """Allows to order a Load Balancer + + + """ + + pkg_name = 'Load Balancer As A Service (LBaaS)' + package_id = self.get_package_id(pkg_name) + prices = self.get_item_prices(package_id) + + # Find and select a subnet id if it was not specified. + if subnet_id is None: + subnet_id = self.get_subnet_id(datacenter) + + # Build the configuration of the order + orderData = { + 'complexType': 'SoftLayer_Container_Product_Order_Network_LoadBalancer_AsAService', + 'name': name, + 'description': desc, + 'location': datacenter, + 'packageId': package_id, + 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'prices': [{'id': price_id} for price_id in prices], + 'protocolConfigurations': protocols, + 'subnets': [{'id': subnet_id}] + } + + try: + # If verify=True it will check your order for errors. + # It will order the lbaas if False. + if verify: + response = self.client['Product_Order'].verifyOrder(orderData) + else: + response = self.client['Product_Order'].placeOrder(orderData) + + return response + except SoftLayer.SoftLayerAPIError as e: + print("Unable to place the order: %s, %s" % (e.faultCode, e.faultString)) + + + def lbaas_order_options(self): + _filter = {'keyName': {'operation': self.package_keyname}} + mask = "mask[id,keyName,name,items[prices],regions[location[location[groups]]]]" + package = self.client.call('SoftLayer_Product_Package', 'getAllObjects', filter=_filter, mask=mask) + return package.pop() + + # Old things below this line def get_lb_pkgs(self): From 4158eb738c0626dab9092b3148f079de1840857b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 31 Jul 2019 11:18:54 -0400 Subject: [PATCH 0128/1582] Remove VpnAllowedFlag. --- SoftLayer/CLI/user/detail.py | 1 - SoftLayer/fixtures/SoftLayer_User_Customer.py | 1 - 2 files changed, 2 deletions(-) diff --git a/SoftLayer/CLI/user/detail.py b/SoftLayer/CLI/user/detail.py index 11b55546a..3754b552c 100644 --- a/SoftLayer/CLI/user/detail.py +++ b/SoftLayer/CLI/user/detail.py @@ -81,7 +81,6 @@ def basic_info(user, keys): if user.get('parentId', False): table.add_row(['Parent User', utils.lookup(user, 'parent', 'username')]) table.add_row(['Status', utils.lookup(user, 'userStatus', 'name')]) - table.add_row(['PPTP VPN', user.get('pptpVpnAllowedFlag', 'No')]) table.add_row(['SSL VPN', user.get('sslVpnAllowedFlag', 'No')]) for login in user.get('unsuccessfulLogins', {}): login_string = "%s From: %s" % (login.get('createDate'), login.get('ipAddress')) diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 32dc17704..204d56a9a 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -16,7 +16,6 @@ 'parent': {'id': 167758, 'username': 'SL12345'}, 'parentId': 167758, 'postalCode': '77002', - 'pptpVpnAllowedFlag': False, 'sslVpnAllowedFlag': True, 'state': 'TX', 'statusDate': None, From 742be8f184cc7512f1846bc63e1a33eda00246e8 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 31 Jul 2019 15:56:15 -0400 Subject: [PATCH 0129/1582] Fix coverage issue. --- SoftLayer/CLI/user/detail.py | 3 ++- SoftLayer/fixtures/SoftLayer_User_Customer.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/user/detail.py b/SoftLayer/CLI/user/detail.py index 3754b552c..9486525a2 100644 --- a/SoftLayer/CLI/user/detail.py +++ b/SoftLayer/CLI/user/detail.py @@ -80,7 +80,8 @@ def basic_info(user, keys): table.add_row(['Phone Number', user.get('officePhone')]) if user.get('parentId', False): table.add_row(['Parent User', utils.lookup(user, 'parent', 'username')]) - table.add_row(['Status', utils.lookup(user, 'userStatus', 'name')]) + table.add_row( + ['Status', utils.lookup(user, 'userStatus', 'name')]) table.add_row(['SSL VPN', user.get('sslVpnAllowedFlag', 'No')]) for login in user.get('unsuccessfulLogins', {}): login_string = "%s From: %s" % (login.get('createDate'), login.get('ipAddress')) diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 204d56a9a..4b30ba326 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -13,7 +13,8 @@ 'isMasterUserFlag': False, 'lastName': 'Testerson', 'openIdConnectUserName': 'test@us.ibm.com', - 'parent': {'id': 167758, 'username': 'SL12345'}, + 'parent': { + 'id': 167758, 'username': 'SL12345'}, 'parentId': 167758, 'postalCode': '77002', 'sslVpnAllowedFlag': True, From c3f363826cf471d0a0065aa5a295a3b4e5251c2a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 31 Jul 2019 15:24:21 -0500 Subject: [PATCH 0130/1582] lb ordering done --- SoftLayer/CLI/loadbal/order.py | 76 +++++++++++++++++++++-------- SoftLayer/managers/load_balancer.py | 42 ++++++++-------- 2 files changed, 80 insertions(+), 38 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 71440063c..f04ab2b68 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -9,36 +9,74 @@ +def parse_proto(ctx, param, value): + proto = {'protocol': 'HTTP', 'port': 80} + splitout = value.split(':') + if len(splitout) != 2: + raise exceptions.ArgumentError("{}={} is not properly formatted.".format(param, value)) + proto['protocol'] = splitout[0] + proto['port'] = int(splitout[1]) + return proto + + @click.command() -@click.argument('identifier') +@click.option('--name', '-n', help='Label for this loadbalancer.', required=True) +@click.option('--datacenter', '-d', help='Datacenter shortname (dal13).', required=True) +@click.option('--label', '-l', help='A descriptive label for this loadbalancer.') +@click.option('--frontend', '-f', required=True, default='HTTP:80', show_default=True, callback=parse_proto, + help='PROTOCOL:PORT string for incoming internet connections.') +@click.option('--backend', '-b', required=True, default='HTTP:80', show_default=True, callback=parse_proto, + help='PROTOCOL:PORT string for connecting to backend servers.') +@click.option('--method', '-m', help="Balancing Method.", default='ROUNDROBIN', show_default=True, + type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) +@click.option('--subnet', '-s', required=True, + help="Private subnet Id to order the LB on. See `slcli lb order-options`") +@click.option('--public', is_flag=True, default=False, show_default=True, help="Use a Public to Public loadbalancer.") +@click.option('--verify', is_flag=True, default=False, show_default=True, + help="Only verify an order, dont actually create one.") @environment.pass_env -def order(env, identifier): - """Creates a LB""" - print("Nothing yet") +def order(env, **args): + """Creates a LB. Protocols supported are TCP, HTTP, and HTTPS.""" + mgr = SoftLayer.LoadBalancerManager(env.client) - package_name = 'Load Balancer As A Service (LBaaS)' - location = 'MEXICO' - name = 'My-LBaaS-name' - description = 'A description sample' - # Set False for private network - is_public = True + location = args.get('datacenter') + name = args.get('name') + description = args.get('label', None) + + backend = args.get('backend') + frontend = args.get('frontend') protocols = [ { - "backendPort": 80, - "backendProtocol": "HTTP", - "frontendPort": 8080, - "frontendProtocol": "HTTP", - "loadBalancingMethod": "ROUNDROBIN", # ROUNDROBIN, LEASTCONNECTION, WEIGHTED_RR + "backendPort": backend.get('port'), + "backendProtocol": backend.get('protocol'), + "frontendPort": frontend.get('port'), + "frontendProtocol": frontend.get('protocol'), + "loadBalancingMethod": args.get('method'), "maxConn": 1000 } ] # remove verify=True to place the order - receipt = lbaas.order_lbaas(package_name, location, name, description, - protocols, public=is_public, verify=True) + receipt = mgr.order_lbaas(location, name, description, protocols, args.get('subnet'), + public=args.get('public'), verify=args.get('verify')) + table = parse_receipt(receipt) + env.fout(table) + + +def parse_receipt(receipt): + table = formatting.KeyValueTable(['Item', 'Cost'], title="Order: {}".format(receipt.get('orderId', 'Quote'))) + if receipt.get('prices'): + for price in receipt.get('prices'): + table.add_row([price['item']['description'], price['hourlyRecurringFee']]) + elif receipt.get('orderDetails'): + for price in receipt['orderDetails']['prices']: + table.add_row([price['item']['description'], price['hourlyRecurringFee']]) + + return table + @click.command() @@ -87,7 +125,7 @@ def order_options(env, datacenter): # Vlan/Subnet Lookups mask = "mask[networkVlan,podName,addressSpace]" subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.KeyValueTable(['Subnet', 'Vlan']) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) for subnet in subnets: # Only show these types, easier to filter here than in an API call. @@ -95,7 +133,7 @@ def order_options(env, datacenter): continue space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) - subnet_table.add_row([space, vlan]) + subnet_table.add_row([subnet.get('id'), space, vlan]) this_table.add_row([price_table, subnet_table]) env.fout(this_table) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 0430a179d..4d379385b 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -5,7 +5,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer import utils +from SoftLayer.managers import ordering class LoadBalancerManager(utils.IdentifierMixin, object): @@ -180,19 +182,26 @@ def remove_lb_listener(self, identifier, listener): identifier, [listener]) return result - def order_lbaas(self, datacenter, name, desc, protocols, subnet_id=None, public=False, verify=False): + def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False, verify=False): """Allows to order a Load Balancer - + :param datacenter: Shortname for the SoftLayer datacenter to order in. + :param name: Identifier for the new LB. + :param desc: Optional description for the lb. + :param protocols: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ + :param subnet_id: Id of the subnet for this new LB to live on. + :param public: Use Public side for the backend. + :param verify: Don't actually order if True. """ + order_mgr = ordering.OrderingManager(self.client) pkg_name = 'Load Balancer As A Service (LBaaS)' - package_id = self.get_package_id(pkg_name) - prices = self.get_item_prices(package_id) + package = order_mgr.get_package_by_key(self.package_keyname, mask='mask[id,keyName,itemPrices]') - # Find and select a subnet id if it was not specified. - if subnet_id is None: - subnet_id = self.get_subnet_id(datacenter) + prices = [] + for price in package.get('itemPrices'): + if not price.get('locationGroupId', False): + prices.append(price.get('id')) # Build the configuration of the order orderData = { @@ -200,24 +209,19 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id=None, public= 'name': name, 'description': desc, 'location': datacenter, - 'packageId': package_id, + 'packageId': package.get('id'), 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': price_id} for price_id in prices], 'protocolConfigurations': protocols, 'subnets': [{'id': subnet_id}] } - try: - # If verify=True it will check your order for errors. - # It will order the lbaas if False. - if verify: - response = self.client['Product_Order'].verifyOrder(orderData) - else: - response = self.client['Product_Order'].placeOrder(orderData) - - return response - except SoftLayer.SoftLayerAPIError as e: - print("Unable to place the order: %s, %s" % (e.faultCode, e.faultString)) + + if verify: + response = self.client['Product_Order'].verifyOrder(orderData) + else: + response = self.client['Product_Order'].placeOrder(orderData) + return response def lbaas_order_options(self): From 9322d60b511bfb6ab00f38a46086d4e081c3edd1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 1 Aug 2019 13:08:30 -0500 Subject: [PATCH 0131/1582] cancel lb support --- SoftLayer/CLI/loadbal/order.py | 15 +- SoftLayer/managers/load_balancer.py | 312 ++-------------------------- 2 files changed, 25 insertions(+), 302 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index f04ab2b68..f2bf028a6 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -140,6 +140,17 @@ def order_options(env, datacenter): @click.command() +@click.argument('identifier') @environment.pass_env -def cancel(env, identifier, **args): - print("Nothing yet") \ No newline at end of file +def cancel(env, identifier): + """Cancels a LBaaS instance""" + + mgr = SoftLayer.LoadBalancerManager(env.client) + uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + + + try: + result = mgr.cancel_lbaas(uuid) + click.secho("LB {} canceled succesfully.".format(identifier), fg='green') + except SoftLayerAPIError as e: + click.secho("ERROR: {}".format(e.faultString), fg='red') diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 4d379385b..25bb44a71 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -76,7 +76,11 @@ def get_lb(self, identifier, mask=None): lb['health'] = health return lb - def get_lb_monitors(self, identifier, mask=None): + def get_lb_monitors(self, identifier): + """Get a LBaaS instance's health checks + + :param identifier: Id of the LBaaS instance (not UUID) + """ health = self.lbaas.getHealthMonitors(id=identifier) return health @@ -195,7 +199,6 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False """ order_mgr = ordering.OrderingManager(self.client) - pkg_name = 'Load Balancer As A Service (LBaaS)' package = order_mgr.get_package_by_key(self.package_keyname, mask='mask[id,keyName,itemPrices]') prices = [] @@ -225,309 +228,18 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False def lbaas_order_options(self): + """Gets the options to order a LBaaS instance.""" _filter = {'keyName': {'operation': self.package_keyname}} mask = "mask[id,keyName,name,items[prices],regions[location[location[groups]]]]" package = self.client.call('SoftLayer_Product_Package', 'getAllObjects', filter=_filter, mask=mask) return package.pop() - -# Old things below this line - - def get_lb_pkgs(self): - """Retrieves the local load balancer packages. - - :returns: A dictionary containing the load balancer packages - """ - - _filter = {'items': {'description': - utils.query_filter('*Load Balancer*')}} - - packages = self.prod_pkg.getItems(id=0, filter=_filter) - pkgs = [] - for package in packages: - if not package['description'].startswith('Global'): - pkgs.append(package) - return pkgs - - def get_hc_types(self): - """Retrieves the health check type values. - - :returns: A dictionary containing the health check types - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_Health_Check_Type'] - return svc.getAllObjects() - - def get_routing_methods(self): - """Retrieves the load balancer routing methods. - - :returns: A dictionary containing the load balancer routing methods - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_Routing_Method'] - return svc.getAllObjects() - - def get_routing_types(self): - """Retrieves the load balancer routing types. - - :returns: A dictionary containing the load balancer routing types - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_Routing_Type'] - return svc.getAllObjects() - - def _get_location(self, datacenter_name): - """Returns the location of the specified datacenter. - - :param string datacenter_name: The datacenter to create - the loadbalancer in - - :returns: the location id of the given datacenter - """ - - datacenters = self.client['Location'].getDataCenters() - for datacenter in datacenters: - if datacenter['name'] == datacenter_name: - return datacenter['id'] - return 'FIRST_AVAILABLE' - - def cancel_lb(self, loadbal_id): - """Cancels the specified load balancer. - - :param int loadbal_id: Load Balancer ID to be cancelled. - """ - - lb_billing = self.lb_svc.getBillingItem(id=loadbal_id) - billing_id = lb_billing['id'] - billing_item = self.client['Billing_Item'] - return billing_item.cancelService(id=billing_id) - - def add_local_lb(self, price_item_id, datacenter): - """Creates a local load balancer in the specified data center. - - :param int price_item_id: The price item ID for the load balancer - :param string datacenter: The datacenter to create the loadbalancer in - :returns: A dictionary containing the product order - """ - - product_order = { - 'complexType': 'SoftLayer_Container_Product_Order_Network_' - 'LoadBalancer', - 'quantity': 1, - 'packageId': 0, - "location": self._get_location(datacenter), - 'prices': [{'id': price_item_id}] - } - return self.client['Product_Order'].placeOrder(product_order) - - - - def get_local_lb(self, loadbal_id, **kwargs): - """Returns a specified local load balancer given the id. - - :param int loadbal_id: The id of the load balancer to retrieve - :returns: A dictionary containing the details of the load balancer - """ - - if 'mask' not in kwargs: - kwargs['mask'] = ('loadBalancerHardware[datacenter], ' - 'ipAddress, virtualServers[serviceGroups' - '[routingMethod,routingType,services' - '[healthChecks[type], groupReferences,' - ' ipAddress]]]') - - return self.lb_svc.getObject(id=loadbal_id, **kwargs) - - def delete_service(self, service_id): - """Deletes a service from the loadbal_id. - - :param int service_id: The id of the service to delete - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_Service'] - - return svc.deleteObject(id=service_id) - - def delete_service_group(self, group_id): - """Deletes a service group from the loadbal_id. - - :param int group_id: The id of the service group to delete - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_VirtualServer'] - - return svc.deleteObject(id=group_id) - - def toggle_service_status(self, service_id): - """Toggles the service status. - - :param int service_id: The id of the service to delete - """ - - svc = self.client['Network_Application_Delivery_Controller_' - 'LoadBalancer_Service'] - return svc.toggleStatus(id=service_id) - - def edit_service(self, loadbal_id, service_id, ip_address_id=None, - port=None, enabled=None, hc_type=None, weight=None): - """Edits an existing service properties. - - :param int loadbal_id: The id of the loadbal where the service resides - :param int service_id: The id of the service to edit - :param string ip_address: The ip address of the service - :param int port: the port of the service - :param bool enabled: enable or disable the search - :param int hc_type: The health check type - :param int weight: the weight to give to the service - """ - - _filter = { - 'virtualServers': { - 'serviceGroups': { - 'services': {'id': utils.query_filter(service_id)}}}} - - mask = 'serviceGroups[services[groupReferences,healthChecks]]' - - virtual_servers = self.lb_svc.getVirtualServers(id=loadbal_id, - filter=_filter, - mask=mask) - - for service in virtual_servers[0]['serviceGroups'][0]['services']: - if service['id'] == service_id: - if enabled is not None: - service['enabled'] = int(enabled) - if port is not None: - service['port'] = port - if weight is not None: - service['groupReferences'][0]['weight'] = weight - if hc_type is not None: - service['healthChecks'][0]['healthCheckTypeId'] = hc_type - if ip_address_id is not None: - service['ipAddressId'] = ip_address_id - - template = {'virtualServers': list(virtual_servers)} - - load_balancer = self.lb_svc.editObject(template, id=loadbal_id) - return load_balancer - - def add_service(self, loadbal_id, service_group_id, ip_address_id, - port=80, enabled=True, hc_type=21, weight=1): - """Adds a new service to the service group. - - :param int loadbal_id: The id of the loadbal where the service resides - :param int service_group_id: The group to add the service to - :param int ip_address id: The ip address ID of the service - :param int port: the port of the service - :param bool enabled: Enable or disable the service - :param int hc_type: The health check type - :param int weight: the weight to give to the service - """ - kwargs = utils.NestedDict({}) - kwargs['mask'] = ('virtualServers[' - 'serviceGroups[services[groupReferences]]]') - - load_balancer = self.lb_svc.getObject(id=loadbal_id, **kwargs) - virtual_servers = load_balancer['virtualServers'] - for virtual_server in virtual_servers: - if virtual_server['id'] == service_group_id: - service_template = { - 'enabled': int(enabled), - 'port': port, - 'ipAddressId': ip_address_id, - 'healthChecks': [ - { - 'healthCheckTypeId': hc_type - } - ], - 'groupReferences': [ - { - 'weight': weight - } - ] - } - services = virtual_server['serviceGroups'][0]['services'] - services.append(service_template) - - return self.lb_svc.editObject(load_balancer, id=loadbal_id) - - def add_service_group(self, lb_id, allocation=100, port=80, - routing_type=2, routing_method=10): - """Adds a new service group to the load balancer. - - :param int loadbal_id: The id of the loadbal where the service resides - :param int allocation: percent of connections to allocate toward the - group - :param int port: the port of the service group - :param int routing_type: the routing type to set on the service group - :param int routing_method: The routing method to set on the group - """ - - mask = 'virtualServers[serviceGroups[services[groupReferences]]]' - load_balancer = self.lb_svc.getObject(id=lb_id, mask=mask) - service_template = { - 'port': port, - 'allocation': allocation, - 'serviceGroups': [ - { - 'routingTypeId': routing_type, - 'routingMethodId': routing_method - } - ] - } - - load_balancer['virtualServers'].append(service_template) - return self.lb_svc.editObject(load_balancer, id=lb_id) - - def edit_service_group(self, loadbal_id, group_id, allocation=None, - port=None, routing_type=None, routing_method=None): - """Edit an existing service group. - - :param int loadbal_id: The id of the loadbal where the service resides - :param int group_id: The id of the service group - :param int allocation: the % of connections to allocate to the group - :param int port: the port of the service group - :param int routing_type: the routing type to set on the service group - :param int routing_method: The routing method to set on the group - """ - - mask = 'virtualServers[serviceGroups[services[groupReferences]]]' - - load_balancer = self.lb_svc.getObject(id=loadbal_id, mask=mask) - virtual_servers = load_balancer['virtualServers'] - - for virtual_server in virtual_servers: - if virtual_server['id'] == group_id: - service_group = virtual_server['serviceGroups'][0] - if allocation is not None: - virtual_server['allocation'] = allocation - if port is not None: - virtual_server['port'] = port - if routing_type is not None: - service_group['routingTypeId'] = routing_type - if routing_method is not None: - service_group['routingMethodId'] = routing_method - break - - return self.lb_svc.editObject(load_balancer, id=loadbal_id) - - def reset_service_group(self, loadbal_id, group_id): - """Resets all the connections on the service group. - - :param int loadbal_id: The id of the loadbal - :param int group_id: The id of the service group to reset + def cancel_lbaas(self, uuid): + """Cancels a LBaaS instance. + + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_LoadBalancer/cancelLoadBalancer/ + :param uuid string: UUID of the LBaaS instance to cancel """ - _filter = {'virtualServers': {'id': utils.query_filter(group_id)}} - virtual_servers = self.lb_svc.getVirtualServers(id=loadbal_id, - filter=_filter, - mask='serviceGroups') - actual_id = virtual_servers[0]['serviceGroups'][0]['id'] + return self.lbaas.cancelLoadBalancer(uuid) - svc = self.client['Network_Application_Delivery_Controller' - '_LoadBalancer_Service_Group'] - return svc.kickAllConnections(id=actual_id) From fe403e1d709231a560b88f858cb96d5a92c8dc98 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 5 Aug 2019 16:56:45 -0500 Subject: [PATCH 0132/1582] tox style fixes --- SoftLayer/CLI/loadbal/__init__.py | 1 - SoftLayer/CLI/loadbal/detail.py | 84 ++++++------ SoftLayer/CLI/loadbal/health.py | 17 +-- SoftLayer/CLI/loadbal/list.py | 26 ++-- SoftLayer/CLI/loadbal/members.py | 33 ++--- SoftLayer/CLI/loadbal/ns_detail.py | 27 ++-- SoftLayer/CLI/loadbal/ns_list.py | 9 +- SoftLayer/CLI/loadbal/order.py | 30 ++--- SoftLayer/CLI/loadbal/pools.py | 104 ++++++++------- SoftLayer/managers/load_balancer.py | 57 ++++---- tests/managers/loadbal_tests.py | 196 ---------------------------- 11 files changed, 185 insertions(+), 399 deletions(-) diff --git a/SoftLayer/CLI/loadbal/__init__.py b/SoftLayer/CLI/loadbal/__init__.py index 77d12e33d..9c48549fc 100644 --- a/SoftLayer/CLI/loadbal/__init__.py +++ b/SoftLayer/CLI/loadbal/__init__.py @@ -1,2 +1 @@ """Load balancers.""" - diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 545c19cde..eb832d594 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer import utils -from pprint import pprint as pp + @click.command() @click.argument('identifier') @@ -13,67 +13,64 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) - lb = mgr.get_lb(lbid) - # pp(lb) - if lb.get('previousErrorText'): - print("THERE WAS AN ERROR") - print(lb.get('previousErrorText')) - table = lbaas_table(lb) + _, lbid = mgr.get_lbaas_uuid_id(identifier) + this_lb = mgr.get_lb(lbid) + if this_lb.get('previousErrorText'): + print(this_lb.get('previousErrorText')) + table = lbaas_table(this_lb) env.fout(table) -def lbaas_table(lb): +def lbaas_table(this_lb): """Generates a table from a list of LBaaS devices""" table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['Id', lb.get('id')]) - table.add_row(['UUI', lb.get('uuid')]) - table.add_row(['Address', lb.get('address')]) - table.add_row(['Location', utils.lookup(lb, 'datacenter', 'longName')]) - table.add_row(['Description', lb.get('description')]) - table.add_row(['Name', lb.get('name')]) - table.add_row(['Status', "{} / {}".format(lb.get('provisioningStatus'), lb.get('operatingStatus'))]) + table.add_row(['Id', this_lb.get('id')]) + table.add_row(['UUI', this_lb.get('uuid')]) + table.add_row(['Address', this_lb.get('address')]) + table.add_row(['Location', utils.lookup(this_lb, 'datacenter', 'longName')]) + table.add_row(['Description', this_lb.get('description')]) + table.add_row(['Name', this_lb.get('name')]) + table.add_row(['Status', "{} / {}".format(this_lb.get('provisioningStatus'), this_lb.get('operatingStatus'))]) # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) - for hp in lb.get('healthMonitors', []): + for health in this_lb.get('healthMonitors', []): hp_table.add_row([ - hp.get('uuid'), - hp.get('interval'), - hp.get('maxRetries'), - hp.get('monitorType'), - hp.get('timeout'), - utils.clean_time(hp.get('modifyDate')), - hp.get('provisioningStatus') + health.get('uuid'), + health.get('interval'), + health.get('maxRetries'), + health.get('monitorType'), + health.get('timeout'), + utils.clean_time(health.get('modifyDate')), + health.get('provisioningStatus') ]) table.add_row(['Checks', hp_table]) # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ - l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active' ]) - for l7 in lb.get('l7Pools', []): + l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) + for layer7 in this_lb.get('l7Pools', []): l7_table.add_row([ - l7.get('id'), - l7.get('uuid'), - l7.get('loadBalancingAlgorithm'), - l7.get('name'), - l7.get('protocol'), - utils.clean_time(l7.get('modifyDate')), - l7.get('provisioningStatus') + layer7.get('id'), + layer7.get('uuid'), + layer7.get('loadBalancingAlgorithm'), + layer7.get('name'), + layer7.get('protocol'), + utils.clean_time(layer7.get('modifyDate')), + layer7.get('provisioningStatus') ]) table.add_row(['L7 Pools', l7_table]) pools = {} # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) - for listener in lb.get('listeners', []): + for listener in this_lb.get('listeners', []): pool = listener.get('defaultPool') priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) pools[pool['uuid']] = priv_map mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) - pool_table = formatting.Table(['Address', ]) listener_table.add_row([ listener.get('uuid'), listener.get('connectionLimit', 'None'), @@ -86,10 +83,10 @@ def lbaas_table(lb): # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] - for uuid in pools.keys(): + for uuid in pools: member_col.append(pools[uuid]) member_table = formatting.Table(member_col) - for member in lb.get('members', []): + for member in this_lb.get('members', []): row = [ member.get('uuid'), member.get('address'), @@ -97,19 +94,20 @@ def lbaas_table(lb): utils.clean_time(member.get('modifyDate')), member.get('provisioningStatus') ] - for uuid in pools.keys(): - row.append(getMemberHp(lb.get('health'), member.get('uuid'), uuid)) + for uuid in pools: + row.append(get_member_hp(this_lb.get('health'), member.get('uuid'), uuid)) member_table.add_row(row) - table.add_row(['Members',member_table]) + table.add_row(['Members', member_table]) # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_SSLCipher/ ssl_table = formatting.Table(['Id', 'Name']) - for ssl in lb.get('sslCiphers', []): + for ssl in this_lb.get('sslCiphers', []): ssl_table.add_row([ssl.get('id'), ssl.get('name')]) table.add_row(['Ciphers', ssl_table]) return table -def getMemberHp(checks, member_uuid, pool_uuid): + +def get_member_hp(checks, member_uuid, pool_uuid): """Helper function to find a members health in a given pool :param checks list: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Pool/#healthMonitor @@ -123,4 +121,4 @@ def getMemberHp(checks, member_uuid, pool_uuid): if hp_member.get('uuid') == member_uuid: status = hp_member.get('status') - return status \ No newline at end of file + return status diff --git a/SoftLayer/CLI/loadbal/health.py b/SoftLayer/CLI/loadbal/health.py index d487ee535..19826ea98 100644 --- a/SoftLayer/CLI/loadbal/health.py +++ b/SoftLayer/CLI/loadbal/health.py @@ -4,27 +4,26 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers from SoftLayer import utils -from pprint import pprint as pp + @click.command() @click.argument('identifier') @click.option('--uuid', required=True, help="Health check UUID to modify.") -@click.option('--interval', '-i', type=click.IntRange(2, 60), help="Seconds between checks. [2-60]") +@click.option('--interval', '-i', type=click.IntRange(2, 60), help="Seconds between checks. [2-60]") @click.option('--retry', '-r', type=click.IntRange(1, 10), help="Number of times before marking as DOWN. [1-10]") @click.option('--timeout', '-t', type=click.IntRange(1, 59), help="Seconds to wait for a connection. [1-59]") @click.option('--url', '-u', help="Url path for HTTP/HTTPS checks.") @environment.pass_env -def cli(env, identifier, uuid, interval, retry, timeout, url): +def cli(env, identifier, uuid, interval, retry, timeout, url): """Manage LBaaS health checks.""" if not any([interval, retry, timeout, url]): raise exceptions.ArgumentError("Specify either interval, retry, timeout, url") # map parameters to expected API names - template = {'healthMonitorUuid': uuid, 'interval': interval, 'maxRetries': retry, 'timeout': timeout, 'urlPath': url} + template = {'healthMonitorUuid': uuid, 'interval': interval, + 'maxRetries': retry, 'timeout': timeout, 'urlPath': url} # Removes those empty values clean_template = {k: v for k, v in template.items() if v is not None} @@ -49,7 +48,6 @@ def cli(env, identifier, uuid, interval, retry, timeout, url): check['timeout'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'timeout') check['urlPath'] = utils.lookup(listener, 'defaultPool', 'healthMonitor', 'urlPath') - if url and check['backendProtocol'] == 'TCP': raise exceptions.ArgumentError('--url cannot be used with TCP checks') @@ -57,12 +55,9 @@ def cli(env, identifier, uuid, interval, retry, timeout, url): for key in clean_template.keys(): check[key] = clean_template[key] - result = mgr.updateLoadBalancerHealthMonitors(lb_uuid, [check]) + result = mgr.update_lb_health_monitors(lb_uuid, [check]) if result: click.secho('Health Check {} updated successfully'.format(uuid), fg='green') else: click.secho('ERROR: Failed to update {}'.format(uuid), fg='red') - - - diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py index 228a98ec6..4eb2f5731 100644 --- a/SoftLayer/CLI/loadbal/list.py +++ b/SoftLayer/CLI/loadbal/list.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer import utils -from pprint import pprint as pp + @click.command() @environment.pass_env @@ -22,9 +22,9 @@ def cli(env): env.fout("No LBaaS devices found") -def location_sort(x): +def location_sort(location): """Quick function that just returns the datacenter longName for sorting""" - return utils.lookup(x, 'datacenter', 'longName') + return utils.lookup(location, 'datacenter', 'longName') def generate_lbaas_table(lbaas): @@ -36,19 +36,17 @@ def generate_lbaas_table(lbaas): table.align['Address'] = 'l' table.align['Description'] = 'l' table.align['Location'] = 'l' - for lb in sorted(lbaas,key=location_sort): - print("PUBLIC: {}".format(lb.get('isPublic'))) + for this_lb in sorted(lbaas, key=location_sort): table.add_row([ - lb.get('id'), - utils.lookup(lb, 'datacenter', 'longName'), - lb.get('address'), - lb.get('description'), - 'Yes' if lb.get('isPublic', 1) == 1 else 'No', - utils.clean_time(lb.get('createDate')), - lb.get('memberCount', 0), - lb.get('listenerCount', 0) + this_lb.get('id'), + utils.lookup(this_lb, 'datacenter', 'longName'), + this_lb.get('address'), + this_lb.get('description'), + 'Yes' if this_lb.get('isPublic', 1) == 1 else 'No', + utils.clean_time(this_lb.get('createDate')), + this_lb.get('memberCount', 0), + this_lb.get('listenerCount', 0) ]) return table - diff --git a/SoftLayer/CLI/loadbal/members.py b/SoftLayer/CLI/loadbal/members.py index 0f0ffe228..bfa81818f 100644 --- a/SoftLayer/CLI/loadbal/members.py +++ b/SoftLayer/CLI/loadbal/members.py @@ -2,16 +2,15 @@ import click import SoftLayer -from SoftLayer.CLI import environment, formatting, helpers +from SoftLayer.CLI import environment from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer import utils -from pprint import pprint as pp + @click.command() @click.argument('identifier') @click.option('--member', '-m', required=True, help="Member UUID") @environment.pass_env -def remove(env, identifier, member): +def remove(env, identifier, member): """Remove a LBaaS member. Member UUID can be found from `slcli lb detail`. @@ -19,14 +18,14 @@ def remove(env, identifier, member): mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + uuid, _ = mgr.get_lbaas_uuid_id(identifier) # Get a member ID to remove try: - result = mgr.delete_lb_member(uuid, member) + mgr.delete_lb_member(uuid, member) click.secho("Member {} removed".format(member), fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') @click.command() @@ -35,11 +34,11 @@ def remove(env, identifier, member): @click.option('--member', '-m', required=True, help="Member IP address.") @click.option('--weight', '-w', default=50, type=int, help="Weight of this member.") @environment.pass_env -def add(env, identifier, private, member, weight): +def add(env, identifier, private, member, weight): """Add a new LBaaS members.""" mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + uuid, _ = mgr.get_lbaas_uuid_id(identifier) # Get a server ID to add to_add = {"weight": weight} if private: @@ -48,15 +47,11 @@ def add(env, identifier, private, member, weight): to_add['publicIpAddress'] = member try: - result = mgr.add_lb_member(uuid, to_add) + mgr.add_lb_member(uuid, to_add) click.secho("Member {} added".format(member), fg='green') - except SoftLayerAPIError as e: - if 'publicIpAddress must be a string' in e.faultString: + except SoftLayerAPIError as exception: + if 'publicIpAddress must be a string' in exception.faultString: click.secho("This LB requires a Public IP address for its members and none was supplied", fg='red') - elif 'privateIpAddress must be a string' in e.faultString: + elif 'privateIpAddress must be a string' in exception.faultString: click.secho("This LB requires a Private IP address for its members and none was supplied", fg='red') - click.secho("ERROR: {}".format(e.faultString), fg='red') - - - - + click.secho("ERROR: {}".format(exception.faultString), fg='red') diff --git a/SoftLayer/CLI/loadbal/ns_detail.py b/SoftLayer/CLI/loadbal/ns_detail.py index 976983b62..537b7c0a4 100644 --- a/SoftLayer/CLI/loadbal/ns_detail.py +++ b/SoftLayer/CLI/loadbal/ns_detail.py @@ -14,25 +14,26 @@ def cli(env, identifier): """Get Netscaler details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - lb = mgr.get_adc(identifier) - table = netscaler_table(lb) + this_lb = mgr.get_adc(identifier) + table = netscaler_table(this_lb) env.fout(table) -def netscaler_table(lb): +def netscaler_table(this_lb): + """Formats the netscaler info table""" table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['Id', lb.get('id')]) - table.add_row(['Type', lb.get('description')]) - table.add_row(['Name', lb.get('name')]) - table.add_row(['Location', utils.lookup(lb, 'datacenter', 'longName')]) - table.add_row(['Managment Ip', lb.get('managementIpAddress')]) - table.add_row(['Root Password', utils.lookup(lb, 'password', 'password')]) - table.add_row(['Primary Ip', lb.get('primaryIpAddress')]) - table.add_row(['License Expiration', utils.clean_time(lb.get('licenseExpirationDate'))]) + table.add_row(['Id', this_lb.get('id')]) + table.add_row(['Type', this_lb.get('description')]) + table.add_row(['Name', this_lb.get('name')]) + table.add_row(['Location', utils.lookup(this_lb, 'datacenter', 'longName')]) + table.add_row(['Managment Ip', this_lb.get('managementIpAddress')]) + table.add_row(['Root Password', utils.lookup(this_lb, 'password', 'password')]) + table.add_row(['Primary Ip', this_lb.get('primaryIpAddress')]) + table.add_row(['License Expiration', utils.clean_time(this_lb.get('licenseExpirationDate'))]) subnet_table = formatting.Table(['Id', 'Subnet', 'Type', 'Space']) - for subnet in lb.get('subnets', []): + for subnet in this_lb.get('subnets', []): subnet_table.add_row([ subnet.get('id'), "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')), @@ -42,7 +43,7 @@ def netscaler_table(lb): table.add_row(['Subnets', subnet_table]) vlan_table = formatting.Table(['Id', 'Number']) - for vlan in lb.get('networkVlans', []): + for vlan in this_lb.get('networkVlans', []): vlan_table.add_row([vlan.get('id'), vlan.get('vlanNumber')]) table.add_row(['Vlans', vlan_table]) diff --git a/SoftLayer/CLI/loadbal/ns_list.py b/SoftLayer/CLI/loadbal/ns_list.py index dd97a2f7e..a37d8bce2 100644 --- a/SoftLayer/CLI/loadbal/ns_list.py +++ b/SoftLayer/CLI/loadbal/ns_list.py @@ -21,9 +21,10 @@ def cli(env): env.fout("No Netscalers") -def location_sort(x): +def location_sort(location): """Quick function that just returns the datacenter longName for sorting""" - return utils.lookup(x, 'datacenter', 'longName') + return utils.lookup(location, 'datacenter', 'longName') + def generate_netscaler_table(netscalers): """Tales a list of SoftLayer_Network_Application_Delivery_Controller and makes a table""" @@ -38,9 +39,7 @@ def generate_netscaler_table(netscalers): adc.get('description'), adc.get('primaryIpAddress'), adc.get('managementIpAddress'), - adc.get('outboundPublicBandwidthUsage',0), + adc.get('outboundPublicBandwidthUsage', 0), utils.clean_time(adc.get('createDate')) ]) return table - - diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index f2bf028a6..2293da8db 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -2,14 +2,16 @@ import click import SoftLayer -from SoftLayer.CLI import environment, formatting, helpers, exceptions +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils -from pprint import pprint as pp - +# pylint: disable=unused-argument def parse_proto(ctx, param, value): + """Parses the frontend and backend cli options""" proto = {'protocol': 'HTTP', 'port': 80} splitout = value.split(':') if len(splitout) != 2: @@ -19,7 +21,6 @@ def parse_proto(ctx, param, value): return proto - @click.command() @click.option('--name', '-n', help='Label for this loadbalancer.', required=True) @click.option('--datacenter', '-d', help='Datacenter shortname (dal13).', required=True) @@ -45,10 +46,9 @@ def order(env, **args): name = args.get('name') description = args.get('label', None) - backend = args.get('backend') frontend = args.get('frontend') - protocols = [ + protocols = [ { "backendPort": backend.get('port'), "backendProtocol": backend.get('protocol'), @@ -60,13 +60,14 @@ def order(env, **args): ] # remove verify=True to place the order - receipt = mgr.order_lbaas(location, name, description, protocols, args.get('subnet'), + receipt = mgr.order_lbaas(location, name, description, protocols, args.get('subnet'), public=args.get('public'), verify=args.get('verify')) table = parse_receipt(receipt) env.fout(table) - + def parse_receipt(receipt): + """Takes an order receipt and nicely formats it for cli output""" table = formatting.KeyValueTable(['Item', 'Cost'], title="Order: {}".format(receipt.get('orderId', 'Quote'))) if receipt.get('prices'): for price in receipt.get('prices'): @@ -78,7 +79,6 @@ def parse_receipt(receipt): return table - @click.command() @click.option('--datacenter', '-d', help="Show only selected datacenter, use shortname (dal13) format.") @environment.pass_env @@ -89,7 +89,6 @@ def order_options(env, datacenter): net_mgr = SoftLayer.NetworkManager(env.client) package = mgr.lbaas_order_options() - tables = [] for region in package['regions']: dc_name = utils.lookup(region, 'location', 'location', 'name') @@ -136,7 +135,7 @@ def order_options(env, datacenter): subnet_table.add_row([subnet.get('id'), space, vlan]) this_table.add_row([price_table, subnet_table]) - env.fout(this_table) + env.fout(this_table) @click.command() @@ -146,11 +145,10 @@ def cancel(env, identifier): """Cancels a LBaaS instance""" mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) - + uuid, _ = mgr.get_lbaas_uuid_id(identifier) try: - result = mgr.cancel_lbaas(uuid) + mgr.cancel_lbaas(uuid) click.secho("LB {} canceled succesfully.".format(identifier), fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index b9a11d929..b8da27ac9 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -2,36 +2,57 @@ import click import SoftLayer -from SoftLayer.CLI import environment, formatting, helpers, exceptions +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer import utils -from pprint import pprint as pp +# pylint: disable=unused-argument def sticky_option(ctx, param, value): + """parses sticky cli option""" if value: return 'SOURCE_IP' return None + +def parse_server(ctx, param, values): + """Splits out the IP, Port, Weight from the --server argument for l7pools""" + servers = [] + for server in values: + splitout = server.split(':') + if len(splitout) != 3: + raise exceptions.ArgumentError( + "--server needs a port and a weight. {} improperly formatted".format(server)) + server = { + 'address': splitout[0], + 'port': splitout[1], + 'weight': splitout[2] + } + servers.append(server) + + return servers + + @click.command() @click.argument('identifier') -@click.option('--frontProtocol', '-P', default='HTTP', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), show_default=True, +@click.option('--frontProtocol', '-P', default='HTTP', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), show_default=True, help="Protocol type to use for incoming connections") @click.option('--backProtocol', '-p', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), help="Protocol type to use when connecting to backend servers. Defaults to whatever --frontProtocol is.") -@click.option('--frontPort', '-f', required=True, type=int, help="Internet side port") -@click.option('--backPort', '-b', required=True, type=int, help="Private side port") -@click.option('--method', '-m', default='ROUNDROBIN', show_default=True, help="Balancing Method", +@click.option('--frontPort', '-f', required=True, type=int, help="Internet side port") +@click.option('--backPort', '-b', required=True, type=int, help="Private side port") +@click.option('--method', '-m', default='ROUNDROBIN', show_default=True, help="Balancing Method", type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") @click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env -def add(env, identifier, **args): +def add(env, identifier, **args): """Adds a listener to the identifier LB""" mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + uuid, _ = mgr.get_lbaas_uuid_id(identifier) new_listener = { 'backendPort': args.get('backport'), @@ -45,10 +66,10 @@ def add(env, identifier, **args): } try: - result = mgr.add_lb_listener(uuid, new_listener) + mgr.add_lb_listener(uuid, new_listener) click.secho("Success", fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') @click.command() @@ -58,7 +79,7 @@ def add(env, identifier, **args): help="Protocol type to use for incoming connections") @click.option('--backProtocol', '-p', type=click.Choice(['HTTP', 'HTTPS', 'TCP']), help="Protocol type to use when connecting to backend servers. Defaults to whatever --frontProtocol is.") -@click.option('--frontPort', '-f', type=int, help="Internet side port") +@click.option('--frontPort', '-f', type=int, help="Internet side port") @click.option('--backPort', '-b', type=int, help="Private side port") @click.option('--method', '-m', help="Balancing Method", type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @@ -67,14 +88,13 @@ def add(env, identifier, **args): @click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env def edit(env, identifier, listener, **args): - """Updates a listener's configuration. + """Updates a listener's configuration. LISTENER should be a UUID, and can be found from `slcli lb detail ` """ mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) - + uuid, _ = mgr.get_lbaas_uuid_id(identifier) new_listener = { 'listenerUuid': listener @@ -91,15 +111,15 @@ def edit(env, identifier, listener, **args): 'sslcert': 'tlsCertificateId' } - for arg in args.keys(): + for arg in args: if args[arg]: new_listener[arg_to_option[arg]] = args[arg] try: - result = mgr.add_lb_listener(uuid, new_listener) + mgr.add_lb_listener(uuid, new_listener) click.secho("Success", fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') @click.command() @@ -108,33 +128,18 @@ def edit(env, identifier, listener, **args): @environment.pass_env def delete(env, identifier, listener): """Removes the listener from identified LBaaS instance - + LISTENER should be a UUID, and can be found from `slcli lb detail ` """ mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + uuid, _ = mgr.get_lbaas_uuid_id(identifier) try: - result = mgr.remove_lb_listener(uuid, listener) + mgr.remove_lb_listener(uuid, listener) click.secho("Success", fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') -def parse_server(ctx, param, values): - """Splits out the IP, Port, Weight from the --server argument for l7pools""" - servers = [] - for server in values: - splitout = server.split(':') - if len(splitout) != 3: - raise exceptions.ArgumentError("--server needs a port and a weight. {} improperly formatted".format(server)) - server = { - 'address': splitout[0], - 'port': splitout[1], - 'weight': splitout[2] - } - servers.append(server) - - return servers @click.command() @click.argument('identifier') @@ -146,7 +151,7 @@ def parse_server(ctx, param, values): show_default=True, help="Protocol type to use for incoming connections") # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Member/ @helpers.multi_option('--server', '-S', callback=parse_server, required=True, - help="Backend servers that are part of this pool. Format is colon deliminated. " \ + help="Backend servers that are part of this pool. Format is colon deliminated. " "BACKEND_IP:PORT:WEIGHT. eg. 10.0.0.1:80:50") # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7HealthMonitor/ @click.option('--healthPath', default='/', show_default=True, help="Health check path.") @@ -164,7 +169,7 @@ def l7pool_add(env, identifier, **args): """ mgr = SoftLayer.LoadBalancerManager(env.client) - uuid, lbid = mgr.get_lbaas_uuid_id(identifier) + uuid, _ = mgr.get_lbaas_uuid_id(identifier) pool_main = { 'name': args.get('name'), @@ -186,11 +191,10 @@ def l7pool_add(env, identifier, **args): } try: - result = mgr.add_lb_l7_pool(uuid, pool_main, pool_members, pool_health, pool_sticky) + mgr.add_lb_l7_pool(uuid, pool_main, pool_members, pool_health, pool_sticky) click.secho("Success", fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') - + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') @click.command() @@ -198,12 +202,12 @@ def l7pool_add(env, identifier, **args): @environment.pass_env def l7pool_del(env, identifier): """Deletes the identified pool - + Identifier is L7Pool Id. NOT the UUID """ mgr = SoftLayer.LoadBalancerManager(env.client) try: - result = mgr.del_lb_l7_pool(identifier) + mgr.del_lb_l7_pool(identifier) click.secho("Success", fg='green') - except SoftLayerAPIError as e: - click.secho("ERROR: {}".format(e.faultString), fg='red') + except SoftLayerAPIError as exception: + click.secho("ERROR: {}".format(exception.faultString), fg='red') diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 25bb44a71..f5c0be9e3 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -5,9 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions -from SoftLayer import utils from SoftLayer.managers import ordering +from SoftLayer import utils class LoadBalancerManager(utils.IdentifierMixin, object): @@ -29,8 +28,6 @@ def __init__(self, client): self.lbaas = self.client['Network_LBaaS_LoadBalancer'] self.package_keyname = 'LBAAS' - - def get_adcs(self, mask=None): """Returns a list of all netscalers. @@ -57,9 +54,9 @@ def get_lbaas(self, mask=None): """ if mask is None: mask = "mask[datacenter,listenerCount,memberCount]" - lb = self.lbaas.getAllObjects(mask=mask) + this_lb = self.lbaas.getAllObjects(mask=mask) - return lb + return this_lb def get_lb(self, identifier, mask=None): """Returns a IBM Cloud LoadBalancer @@ -69,24 +66,25 @@ def get_lb(self, identifier, mask=None): if mask is None: mask = "mask[healthMonitors, l7Pools, members, sslCiphers, " \ "listeners[defaultPool[healthMonitor, members, sessionAffinity],l7Policies]]" - - lb = self.lbaas.getObject(id=identifier, mask=mask) - health = self.lbaas.getLoadBalancerMemberHealth(lb.get('uuid')) - lb['health'] = health - return lb + this_lb = self.lbaas.getObject(id=identifier, mask=mask) + health = self.lbaas.getLoadBalancerMemberHealth(this_lb.get('uuid')) + + this_lb['health'] = health + return this_lb def get_lb_monitors(self, identifier): """Get a LBaaS instance's health checks - + + :param identifier: Id of the LBaaS instance (not UUID) """ health = self.lbaas.getHealthMonitors(id=identifier) return health - def updateLoadBalancerHealthMonitors(self, uuid, checks): + def update_lb_health_monitors(self, uuid, checks): """calls SoftLayer_Network_LBaaS_HealthMonitor::updateLoadBalancerHealthMonitors() - + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_HealthMonitor/updateLoadBalancerHealthMonitors/ https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration/ :param uuid: loadBalancerUuid @@ -104,19 +102,19 @@ def get_lbaas_uuid_id(self, identifier): :return (uuid, id): """ if len(identifier) == 36: - lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") + this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") else: - lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") - return lb['uuid'], lb['id'] + this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") + return this_lb['uuid'], this_lb['id'] def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_Member/deleteLoadBalancerMembers/ :param identifier: UUID of the LBaaS instance - :param member_id: Member UUID to remove. + :param member_id: Member UUID to remove. """ - result = self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', + result = self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', identifier, [member_id]) return result @@ -125,10 +123,10 @@ def add_lb_member(self, identifier, member_id): https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_Member/deleteLoadBalancerMembers/ :param identifier: UUID of the LBaaS instance - :param member_id: Member UUID to remove. + :param member_id: Member UUID to remove. """ - result = self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', + result = self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', identifier, [member_id]) return result @@ -174,7 +172,6 @@ def del_lb_l7_pool(self, identifier): result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'deleteObject', id=identifier) return result - def remove_lb_listener(self, identifier, listener): """Removes a listener to a LBaaS instance @@ -207,26 +204,25 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False prices.append(price.get('id')) # Build the configuration of the order - orderData = { + order_data = { 'complexType': 'SoftLayer_Container_Product_Order_Network_LoadBalancer_AsAService', 'name': name, 'description': desc, 'location': datacenter, 'packageId': package.get('id'), - 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': price_id} for price_id in prices], 'protocolConfigurations': protocols, - 'subnets': [{'id': subnet_id}] + 'subnets': [{'id': subnet_id}], + 'isPublic': public } - if verify: - response = self.client['Product_Order'].verifyOrder(orderData) + response = self.client['Product_Order'].verifyOrder(order_data) else: - response = self.client['Product_Order'].placeOrder(orderData) + response = self.client['Product_Order'].placeOrder(order_data) return response - def lbaas_order_options(self): """Gets the options to order a LBaaS instance.""" _filter = {'keyName': {'operation': self.package_keyname}} @@ -236,10 +232,9 @@ def lbaas_order_options(self): def cancel_lbaas(self, uuid): """Cancels a LBaaS instance. - + https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_LoadBalancer/cancelLoadBalancer/ :param uuid string: UUID of the LBaaS instance to cancel """ return self.lbaas.cancelLoadBalancer(uuid) - diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index f7fd63910..dab77ea9f 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -7,204 +7,8 @@ import SoftLayer from SoftLayer import testing -VIRT_IP_SERVICE = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_VirtualIpAddress') - class LoadBalancerTests(testing.TestCase): def set_up(self): self.lb_mgr = SoftLayer.LoadBalancerManager(self.client) - - def test_get_lb_pkgs(self): - result = self.lb_mgr.get_lb_pkgs() - - self.assertEqual(len(result), 13) - _filter = { - 'items': { - 'description': { - 'operation': '*= Load Balancer' - } - } - } - self.assert_called_with('SoftLayer_Product_Package', 'getItems', - identifier=0, - filter=_filter) - - def test_get_hc_types(self): - result = self.lb_mgr.get_hc_types() - - self.assertEqual(len(result), 6) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Health_Check_Type') - self.assert_called_with(service, 'getAllObjects') - - def test_get_routing_methods(self): - result = self.lb_mgr.get_routing_methods() - - self.assertEqual(len(result), 12) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Routing_Method') - self.assert_called_with(service, 'getAllObjects') - - def test_get_location(self): - id1 = self.lb_mgr._get_location('sjc01') - self.assertEqual(id1, 168642) - - id2 = self.lb_mgr._get_location('dal05') - self.assertEqual(id2, 'FIRST_AVAILABLE') - - def test_get_routing_types(self): - result = self.lb_mgr.get_routing_types() - - self.assertEqual(len(result), 6) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Routing_Type') - self.assert_called_with(service, 'getAllObjects') - - def test_cancel_lb(self): - result = self.lb_mgr.cancel_lb(6327) - - self.assertEqual(result, True) - self.assert_called_with('SoftLayer_Billing_Item', 'cancelService', - identifier=21370814) - - def test_add_local_lb(self): - self.lb_mgr.add_local_lb(6327, 'sjc01') - - args = ({ - 'complexType': 'SoftLayer_Container_Product_Order_Network_' - 'LoadBalancer', - 'quantity': 1, - 'packageId': 0, - "location": 168642, - 'prices': [{'id': 6327}] - },) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', - args=args) - - def test_get_local_lbs(self): - result = self.lb_mgr.get_local_lbs() - - self.assertEqual(len(result), 0) - mask = 'mask[loadBalancerHardware[datacenter],ipAddress]' - self.assert_called_with('SoftLayer_Account', 'getAdcLoadBalancers', - mask=mask) - - def test_get_local_lb(self): - result = self.lb_mgr.get_local_lb(22348) - - self.assertEqual(result['id'], 22348) - mask = ('mask[' - 'loadBalancerHardware[datacenter], ' - 'ipAddress, virtualServers[serviceGroups' - '[routingMethod,routingType,services' - '[healthChecks[type], groupReferences,' - ' ipAddress]]]]') - self.assert_called_with(VIRT_IP_SERVICE, 'getObject', - identifier=22348, - mask=mask) - - def test_delete_service(self): - result = self.lb_mgr.delete_service(1234) - - self.assertEqual(result, True) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Service') - self.assert_called_with(service, 'deleteObject', identifier=1234) - - def test_delete_service_group(self): - result = self.lb_mgr.delete_service_group(1234) - - self.assertEqual(result, True) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_VirtualServer') - self.assert_called_with(service, 'deleteObject', identifier=1234) - - def test_toggle_service_status(self): - result = self.lb_mgr.toggle_service_status(1234) - - self.assertEqual(result, True) - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Service') - self.assert_called_with(service, 'toggleStatus', identifier=1234) - - def test_edit_service(self): - self.lb_mgr.edit_service(12345, 1234, '9.9.9.9', 80, True, 21, 1) - - _filter = { - 'virtualServers': { - 'serviceGroups': { - 'services': { - 'id': { - 'operation': 1234 - } - } - } - } - } - mask = 'mask[serviceGroups[services[groupReferences,healthChecks]]]' - self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers', - identifier=12345, - filter=_filter, - mask=mask) - - self.assert_called_with(VIRT_IP_SERVICE, 'editObject') - - def test_add_service(self): - self.lb_mgr.add_service(12345, 50718, 123, 80, True, 21, 1) - - mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]' - self.assert_called_with(VIRT_IP_SERVICE, 'getObject', - mask=mask, - identifier=12345) - - self.assert_called_with(VIRT_IP_SERVICE, 'editObject', - identifier=12345) - arg = self.calls(VIRT_IP_SERVICE, 'editObject')[0].args[0] - self.assertEqual( - len(arg['virtualServers'][0]['serviceGroups'][0]['services']), - 2) - - def test_edit_service_group(self): - self.lb_mgr.edit_service_group(12345, - group_id=50718, - allocation=100, - port=80, - routing_type=2, - routing_method=10) - - mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]' - self.assert_called_with(VIRT_IP_SERVICE, 'getObject', - identifier=12345, - mask=mask) - - self.assert_called_with(VIRT_IP_SERVICE, 'getObject', identifier=12345) - - def test_add_service_group(self): - self.lb_mgr.add_service_group(12345, 100, 80, 2, 10) - - mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]' - self.assert_called_with(VIRT_IP_SERVICE, 'getObject', - mask=mask, - identifier=12345) - - self.assert_called_with(VIRT_IP_SERVICE, 'editObject', - identifier=12345) - arg = self.calls(VIRT_IP_SERVICE, 'editObject')[0].args[0] - self.assertEqual(len(arg['virtualServers']), 2) - - def test_reset_service_group(self): - result = self.lb_mgr.reset_service_group(12345, group_id=50718) - - self.assertEqual(result, True) - _filter = {'virtualServers': {'id': {'operation': 50718}}} - self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers', - identifier=12345, - filter=_filter, - mask='mask[serviceGroups]') - - service = ('SoftLayer_Network_Application_Delivery_Controller_' - 'LoadBalancer_Service_Group') - self.assert_called_with(service, 'kickAllConnections', - identifier=51758) From 2e738f7e9685b8b4007ef63ff517e9f45dabff37 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 13 Aug 2019 16:18:16 -0500 Subject: [PATCH 0133/1582] Improve hardware cancellation to deal with cases where both a billing item and open cancellation ticket aren't available. --- SoftLayer/managers/hardware.py | 12 +++++++----- tests/managers/hardware_tests.py | 9 +++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 4a52a5236..f5b01646b 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,7 +9,6 @@ import socket import time - import SoftLayer from SoftLayer.decoration import retry from SoftLayer.managers import ordering @@ -86,14 +85,17 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment='', immediate= raise SoftLayer.SoftLayerError("Unable to cancel hardware with running transaction") if 'billingItem' not in hw_billing: - raise SoftLayer.SoftLayerError("Ticket #%s already exists for this server" % - hw_billing['openCancellationTicket']['id']) + if utils.lookup(hw_billing, 'openCancellationTicket', 'id'): + raise SoftLayer.SoftLayerError("Ticket #%s already exists for this server" % + hw_billing['openCancellationTicket']['id']) + raise SoftLayer.SoftLayerError("Cannot locate billing for the server. " + "The server may already be cancelled.") billing_id = hw_billing['billingItem']['id'] if immediate and not hw_billing['hourlyBillingFlag']: - LOGGER.warning("Immediate cancelation of montly servers is not guaranteed." - "Please check the cancelation ticket for updates.") + LOGGER.warning("Immediate cancellation of monthly servers is not guaranteed." + "Please check the cancellation ticket for updates.") result = self.client.call('Billing_Item', 'cancelItem', False, False, cancel_reason, comment, id=billing_id) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 461094be6..120f60601 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -291,6 +291,15 @@ def test_cancel_hardware_no_billing_item(self): 6327) self.assertEqual("Ticket #1234 already exists for this server", str(ex)) + def test_cancel_hardwareno_billing_item_or_ticket(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + mock.return_value = {'id': 987} + + ex = self.assertRaises(SoftLayer.SoftLayerError, + self.hardware.cancel_hardware, + 6327) + self.assertEqual("Cannot locate billing for the server. The server may already be cancelled.", str(ex)) + def test_cancel_hardware_monthly_now(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') mock.return_value = {'id': 987, 'billingItem': {'id': 1234}, From 56474a996ddd5acf902353d83be0230f8cda8030 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 13 Aug 2019 17:49:51 -0500 Subject: [PATCH 0134/1582] Handle missing/empty allocation information when getting bandwidth details for hardware and virtual servers and when building the bandwidth detail tables for them. --- SoftLayer/CLI/hardware/detail.py | 4 +++- SoftLayer/CLI/virt/detail.py | 4 +++- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/vs.py | 2 +- tests/managers/hardware_tests.py | 8 ++++++++ tests/managers/vs/vs_tests.py | 8 ++++++++ 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index e254ad33e..3a55f1b6d 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -99,7 +99,9 @@ def _bw_table(bw_data): allotment = 'N/A' if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': bw_type = 'Public' - allotment = bw_data['allotment'].get('amount', '-') + allotment = utils.lookup(bw_data, 'allotment', 'amount') + if allotment is None: + allotment = '-' table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 53ae7e04d..bf93a8342 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -131,7 +131,9 @@ def _bw_table(bw_data): allotment = 'N/A' if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': bw_type = 'Public' - allotment = bw_data['allotment'].get('amount', '-') + allotment = utils.lookup(bw_data, 'allotment', 'amount') + if allotment is None: + allotment = '-' table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index f5b01646b..fb85c3282 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -698,7 +698,7 @@ def get_bandwidth_allocation(self, instance_id): allotment = self.client.call('Hardware_Server', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) u_mask = "mask[amountIn,amountOut,type]" useage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) - return {'allotment': allotment['allocation'], 'useage': useage} + return {'allotment': allotment.get('allocation'), 'useage': useage} def _get_extra_price_id(items, key_name, hourly, location): diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 85bbdf9d9..de845096b 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1067,7 +1067,7 @@ def get_bandwidth_allocation(self, instance_id): allotment = self.client.call('Virtual_Guest', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) u_mask = "mask[amountIn,amountOut,type]" useage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) - return {'allotment': allotment['allocation'], 'useage': useage} + return {'allotment': allotment.get('allocation'), 'useage': useage} # pylint: disable=inconsistent-return-statements def _get_price_id_for_upgrade(self, package_items, option, value, public=True): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 120f60601..7cff11303 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -448,6 +448,14 @@ def test_get_bandwidth_allocation(self): self.assertEqual(result['allotment']['amount'], '250') self.assertEqual(result['useage'][0]['amountIn'], '.448') + def test_get_bandwidth_allocation_no_allotment(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') + mock.return_value = {} + + result = self.hardware.get_bandwidth_allocation(1234) + + self.assertEqual(None, result['allotment']) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 2816f8b06..14d41966c 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -903,3 +903,11 @@ def test_get_bandwidth_allocation(self): self.assert_called_with('SoftLayer_Virtual_Guest', 'getBillingCycleBandwidthUsage', identifier=1234) self.assertEqual(result['allotment']['amount'], '250') self.assertEqual(result['useage'][0]['amountIn'], '.448') + + def test_get_bandwidth_allocation_no_allotment(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail') + mock.return_value = {} + + result = self.vs.get_bandwidth_allocation(1234) + + self.assertEqual(None, result['allotment']) From c9bb5f0875fd64e00da83b5e7d5ee0ba2c0d0173 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 15 Aug 2019 17:22:29 -0500 Subject: [PATCH 0135/1582] unit tests for lb-manager --- SoftLayer/fixtures/SoftLayer_Account.py | 27 +++++++++++++ ...Network_Application_Delivery_Controller.py | 24 +++++++++++ .../SoftLayer_Network_LBaaS_LoadBalancer.py | 39 ++++++++++++++++++ SoftLayer/managers/load_balancer.py | 9 ----- tests/managers/loadbal_tests.py | 40 +++++++++++++++++++ 5 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index cf884aefd..1c8800628 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -715,3 +715,30 @@ 'endingBalance': '12345.55' } ] + +getApplicationDeliveryControllers = [ + { + 'accountId': 307608, + 'createDate': '2015-05-05T16:23:52-06:00', + 'id': 11449, + 'modifyDate': '2015-05-05T16:24:09-06:00', + 'name': 'SLADC307608-1', + 'typeId': 2, + 'description': 'Citrix NetScaler VPX 10.5 10Mbps Standard', + 'managementIpAddress': '10.11.11.112', + 'outboundPublicBandwidthUsage': '.00365', + 'primaryIpAddress': '19.4.24.16', + 'datacenter': { + 'longName': 'Dallas 9', + 'name': 'dal09', + }, + 'password': { + 'password': 'aaaaa', + 'username': 'root' + }, + 'type': { + 'keyName': 'NETSCALER_VPX', + 'name': 'NetScaler VPX' + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py new file mode 100644 index 000000000..5316e4466 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py @@ -0,0 +1,24 @@ +getObject = { + 'accountId': 307608, + 'createDate': '2015-05-05T16:23:52-06:00', + 'id': 11449, + 'modifyDate': '2015-05-05T16:24:09-06:00', + 'name': 'SLADC307608-1', + 'typeId': 2, + 'description': 'Citrix NetScaler VPX 10.5 10Mbps Standard', + 'managementIpAddress': '10.11.11.112', + 'outboundPublicBandwidthUsage': '.00365', + 'primaryIpAddress': '19.4.24.16', + 'datacenter': { + 'longName': 'Dallas 9', + 'name': 'dal09', + }, + 'password': { + 'password': 'aaaaa', + 'username': 'root' + }, + 'type': { + 'keyName': 'NETSCALER_VPX', + 'name': 'NetScaler VPX' + } +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py new file mode 100644 index 000000000..717a1746d --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -0,0 +1,39 @@ +getObject = { + 'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + } + } +getAllObjects = [getObject] + + +getLoadBalancerMemberHealth = [ + { + 'poolUuid': '1c5f3ba6-ec7d-4cf8-8815-9bb174224a76', + 'membersHealth': [ + { + 'status': 'DOWN', + 'uuid': 'ba23a166-faa4-4eb2-96e7-ef049d65ce60' + } + ] + } +] + +getHealthMonitors = {} \ No newline at end of file diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index f5c0be9e3..69c1034b0 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -73,15 +73,6 @@ def get_lb(self, identifier, mask=None): this_lb['health'] = health return this_lb - def get_lb_monitors(self, identifier): - """Get a LBaaS instance's health checks - - - :param identifier: Id of the LBaaS instance (not UUID) - """ - health = self.lbaas.getHealthMonitors(id=identifier) - return health - def update_lb_health_monitors(self, uuid, checks): """calls SoftLayer_Network_LBaaS_HealthMonitor::updateLoadBalancerHealthMonitors() diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index dab77ea9f..873eb57e0 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -12,3 +12,43 @@ class LoadBalancerTests(testing.TestCase): def set_up(self): self.lb_mgr = SoftLayer.LoadBalancerManager(self.client) + + def test_get_adcs(self): + self.lb_mgr.get_adcs() + self.assert_called_with('SoftLayer_Account', 'getApplicationDeliveryControllers') + + def test_get_adc_masks(self): + self.lb_mgr.get_adcs(mask="mask[id]") + self.assert_called_with('SoftLayer_Account', 'getApplicationDeliveryControllers', mask="mask[id]") + + def test_get_adc(self): + self.lb_mgr.get_adc(1234) + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=1234) + + def test_get_adc_mask(self): + self.lb_mgr.get_adc(1234, mask="mask[id]") + self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=1234, + mask="mask[id]") + + def test_get_lbaas(self): + self.lb_mgr.get_lbaas() + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + + def test_get_lbaas_mask(self): + self.lb_mgr.get_lbaas(mask="mask[id]") + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects', mask="mask[id]") + + def test_get_lb(self): + lb = self.lb_mgr.get_lb(1234) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getObject', identifier=1234) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getLoadBalancerMemberHealth', + args=(lb.get('uuid'),)) + self.assertIsNotNone(lb['health']) + + def test_get_lb_mask(self): + lb = self.lb_mgr.get_lb(1234, mask="mask[id]") + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getObject', identifier=1234, mask="mask[id]") + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getLoadBalancerMemberHealth', + args=(lb.get('uuid'),)) + self.assertIsNotNone(lb['health']) + From bbac83a6b53303fa12695beb40da749100944aa4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 19 Aug 2019 17:17:05 -0500 Subject: [PATCH 0136/1582] #1158 lb manager unit tests --- .../SoftLayer_Network_LBaaS_HealthMonitor.py | 9 ++ .../SoftLayer_Network_LBaaS_L7Pool.py | 2 + .../SoftLayer_Network_LBaaS_Listener.py | 3 + .../SoftLayer_Network_LBaaS_LoadBalancer.py | 5 +- .../SoftLayer_Network_LBaaS_Member.py | 3 + SoftLayer/managers/load_balancer.py | 39 ++++---- tests/managers/loadbal_tests.py | 92 +++++++++++++++++++ 7 files changed, 130 insertions(+), 23 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py new file mode 100644 index 000000000..d3b8770e4 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py @@ -0,0 +1,9 @@ +updateLoadBalancerHealthMonitors = { + 'backendPort': 80, + 'backendProtocol': 'HTTP', + 'healthMonitorUuid': '1a1aa111-4474-4e16-9f02-4de959244444', + 'interval': 50, + 'maxRetries': 10, + 'timeout': 10, + 'urlPath': None +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py new file mode 100644 index 000000000..b3a909a3d --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py @@ -0,0 +1,2 @@ +createL7Pool = {} +deleteObject = {} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py new file mode 100644 index 000000000..d3954ce29 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -0,0 +1,3 @@ +# https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ +updateLoadBalancerProtocols = {} +deleteLoadBalancerProtocols = {} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 717a1746d..53ffd6749 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -36,4 +36,7 @@ } ] -getHealthMonitors = {} \ No newline at end of file +getHealthMonitors = {} + +getLoadBalancer = getObject +cancelLoadBalancer = getObject \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py new file mode 100644 index 000000000..9bc95f2cc --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py @@ -0,0 +1,3 @@ +#Should be sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancer +deleteLoadBalancerMembers = {} +addLoadBalancerMembers = deleteLoadBalancerMembers \ No newline at end of file diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 69c1034b0..5bdc419c5 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -92,7 +92,8 @@ def get_lbaas_uuid_id(self, identifier): :param identifier: either the LB Id, or UUID, this function will return both. :return (uuid, id): """ - if len(identifier) == 36: + # int objects don't have a len property. + if not isinstance(identifier, int) and len(identifier) == 36: this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") else: this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") @@ -105,22 +106,21 @@ def delete_lb_member(self, identifier, member_id): :param identifier: UUID of the LBaaS instance :param member_id: Member UUID to remove. """ - result = self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', - identifier, [member_id]) - return result + return self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', + identifier, [member_id]) - def add_lb_member(self, identifier, member_id): - """Removes a member from a LBaaS instance + + def add_lb_member(self, identifier, service_info): + """Adds a member to a LBaaS instance https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_Member/deleteLoadBalancerMembers/ :param identifier: UUID of the LBaaS instance - :param member_id: Member UUID to remove. + :param service_info: datatypes/SoftLayer_Network_LBaaS_LoadBalancerServerInstanceInfo """ - result = self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', - identifier, [member_id]) + return self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', + identifier, [service_info]) - return result def add_lb_listener(self, identifier, listener): """Adds or update a listener to a LBaaS instance @@ -133,9 +133,8 @@ def add_lb_listener(self, identifier, listener): :param listener: Object with all listener configurations """ - result = self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', - identifier, [listener]) - return result + return self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', + identifier, [listener]) def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance @@ -150,18 +149,15 @@ def add_lb_l7_pool(self, identifier, pool, members, health, session): :param session SoftLayer_Network_LBaaS_L7SessionAffinity: Weather to use affinity """ - result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', - identifier, pool, members, health, session) - - return result + return self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', + identifier, pool, members, health, session) def del_lb_l7_pool(self, identifier): """Deletes a l7 pool :param identifier: Id of the L7Pool """ - result = self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'deleteObject', id=identifier) - return result + return self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'deleteObject', id=identifier) def remove_lb_listener(self, identifier, listener): """Removes a listener to a LBaaS instance @@ -170,9 +166,8 @@ def remove_lb_listener(self, identifier, listener): :param listener: UUID of the Listner to be removed. """ - result = self.client.call('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', - identifier, [listener]) - return result + return self.client.call('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', + identifier, [listener]) def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False, verify=False): """Allows to order a Load Balancer diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 873eb57e0..87970b3c2 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -3,6 +3,9 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. + + A lot of these tests will use junk data because the manager just passes + them directly to the API. """ import SoftLayer from SoftLayer import testing @@ -52,3 +55,92 @@ def test_get_lb_mask(self): args=(lb.get('uuid'),)) self.assertIsNotNone(lb['health']) + def test_updated_lb_health(self): + uuid = '1234' + check = {'backendPort': '80'} + self.lb_mgr.update_lb_health_monitors(uuid, check) + self.assert_called_with('SoftLayer_Network_LBaaS_HealthMonitor', 'updateLoadBalancerHealthMonitors', + args=(uuid, check)) + + def test_get_lbaas_uuid_id_uuid(self): + uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' + my_id = 1111111 + lb_uuid,lb_id = self.lb_mgr.get_lbaas_uuid_id(uuid) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getLoadBalancer', args=(uuid,)) + self.assertEqual(lb_uuid, uuid) + self.assertEqual(lb_id, my_id) + + def test_get_lbaas_uuid_id_id(self): + uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' + my_id = 1111111 + lb_uuid,lb_id = self.lb_mgr.get_lbaas_uuid_id(my_id) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getObject', identifier=my_id) + self.assertEqual(lb_uuid, uuid) + self.assertEqual(lb_id, my_id) + + def test_delete_lb_member(self): + uuid = 'aa-bb-cc' + member_id = 'dd-ee-ff' + self.lb_mgr.delete_lb_member(uuid, member_id) + self.assert_called_with('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', + args=(uuid, [member_id])) + + def test_add_lb_member(self): + uuid = 'aa-bb-cc' + member = {'privateIpAddress': '1.2.3.4'} + self.lb_mgr.add_lb_member(uuid, member) + self.assert_called_with('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', + args=(uuid, [member])) + + def test_add_lb_listener(self): + uuid = 'aa-bb-cc' + listener = {'id': 1} + self.lb_mgr.add_lb_listener(uuid, listener) + self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', + args=(uuid, [listener])) + + def test_add_lb_l7_pool(self): + uuid = 'aa-bb-cc' + pool = {'id': 1} + members = {'id': 1} + health = {'id': 1} + session = {'id': 1} + self.lb_mgr.add_lb_l7_pool(uuid, pool, members, health, session) + self.assert_called_with('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', + args=(uuid, pool, members, health, session)) + + def test_del_lb_l7_pool(self): + uuid = 'aa-bb-cc' + self.lb_mgr.del_lb_l7_pool(uuid) + self.assert_called_with('SoftLayer_Network_LBaaS_L7Pool', 'deleteObject', identifier=uuid) + + def test_remove_lb_listener(self): + uuid = 'aa-bb-cc' + listener = 'dd-ee-ff' + self.lb_mgr.remove_lb_listener(uuid, listener) + self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', + args=(uuid, [listener])) + + def order_lbaas(self): + datacenter = 'tes01' + name = 'test-lb' + desc = 'my lb' + protocols = {'frontendPort': 80, 'frontendProtocol': 'HTTP'} + subnet_id = 12345 + public = True + verify = False + self.lb_mgr.order_lbaas(datacenter, name, desc, protocols, subnet_id, public, verify) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + verify = True + self.lb_mgr.order_lbaas(datacenter, name, desc, protocols, subnet_id, public, verify) + self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') + + def test_lbaas_order_options(self): + self.lb_mgr.lbaas_order_options() + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + + def test_cancel_lbaas(self): + uuid = 'aa-bb-cc' + self.lb_mgr.cancel_lbaas(uuid) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) + From 22497240b40ddeffff62ddd58f0de8c2acae381b Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 19 Aug 2019 17:43:39 -0500 Subject: [PATCH 0137/1582] #1158 tox style fixes --- ...Network_Application_Delivery_Controller.py | 2 +- .../SoftLayer_Network_LBaaS_HealthMonitor.py | 2 +- .../SoftLayer_Network_LBaaS_L7Pool.py | 2 +- .../SoftLayer_Network_LBaaS_Listener.py | 2 +- .../SoftLayer_Network_LBaaS_LoadBalancer.py | 4 +- .../SoftLayer_Network_LBaaS_Member.py | 4 +- SoftLayer/managers/load_balancer.py | 12 +++--- tests/managers/loadbal_tests.py | 42 ++++++++++++++++--- 8 files changed, 50 insertions(+), 20 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py index 5316e4466..532968d50 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py @@ -21,4 +21,4 @@ 'keyName': 'NETSCALER_VPX', 'name': 'NetScaler VPX' } -} \ No newline at end of file +} diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py index d3b8770e4..74696c7f5 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_HealthMonitor.py @@ -6,4 +6,4 @@ 'maxRetries': 10, 'timeout': 10, 'urlPath': None -} \ No newline at end of file +} diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py index b3a909a3d..2d66fcd5c 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py @@ -1,2 +1,2 @@ createL7Pool = {} -deleteObject = {} \ No newline at end of file +deleteObject = {} diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index d3954ce29..5af3844e1 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -1,3 +1,3 @@ # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ updateLoadBalancerProtocols = {} -deleteLoadBalancerProtocols = {} \ No newline at end of file +deleteLoadBalancerProtocols = {} diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 53ffd6749..d9dcce0ca 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,4 +1,4 @@ -getObject = { +getObject = { 'accountId': 1234, 'address': '01-307608-ams01.clb.appdomain.cloud', 'createDate': '2019-08-12T07:49:43-06:00', @@ -39,4 +39,4 @@ getHealthMonitors = {} getLoadBalancer = getObject -cancelLoadBalancer = getObject \ No newline at end of file +cancelLoadBalancer = getObject diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py index 9bc95f2cc..449b52a67 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Member.py @@ -1,3 +1,3 @@ -#Should be sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancer +# Should be sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancer deleteLoadBalancerMembers = {} -addLoadBalancerMembers = deleteLoadBalancerMembers \ No newline at end of file +addLoadBalancerMembers = deleteLoadBalancerMembers diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 5bdc419c5..072077ef0 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -107,8 +107,7 @@ def delete_lb_member(self, identifier, member_id): :param member_id: Member UUID to remove. """ return self.client.call('SoftLayer_Network_LBaaS_Member', 'deleteLoadBalancerMembers', - identifier, [member_id]) - + identifier, [member_id]) def add_lb_member(self, identifier, service_info): """Adds a member to a LBaaS instance @@ -119,8 +118,7 @@ def add_lb_member(self, identifier, service_info): """ return self.client.call('SoftLayer_Network_LBaaS_Member', 'addLoadBalancerMembers', - identifier, [service_info]) - + identifier, [service_info]) def add_lb_listener(self, identifier, listener): """Adds or update a listener to a LBaaS instance @@ -134,7 +132,7 @@ def add_lb_listener(self, identifier, listener): """ return self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', - identifier, [listener]) + identifier, [listener]) def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance @@ -150,7 +148,7 @@ def add_lb_l7_pool(self, identifier, pool, members, health, session): """ return self.client.call('SoftLayer_Network_LBaaS_L7Pool', 'createL7Pool', - identifier, pool, members, health, session) + identifier, pool, members, health, session) def del_lb_l7_pool(self, identifier): """Deletes a l7 pool @@ -167,7 +165,7 @@ def remove_lb_listener(self, identifier, listener): """ return self.client.call('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', - identifier, [listener]) + identifier, [listener]) def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False, verify=False): """Allows to order a Load Balancer diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 87970b3c2..cf93ed9a6 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -65,7 +65,7 @@ def test_updated_lb_health(self): def test_get_lbaas_uuid_id_uuid(self): uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' my_id = 1111111 - lb_uuid,lb_id = self.lb_mgr.get_lbaas_uuid_id(uuid) + lb_uuid, lb_id = self.lb_mgr.get_lbaas_uuid_id(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getLoadBalancer', args=(uuid,)) self.assertEqual(lb_uuid, uuid) self.assertEqual(lb_id, my_id) @@ -73,7 +73,7 @@ def test_get_lbaas_uuid_id_uuid(self): def test_get_lbaas_uuid_id_id(self): uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' my_id = 1111111 - lb_uuid,lb_id = self.lb_mgr.get_lbaas_uuid_id(my_id) + lb_uuid, lb_id = self.lb_mgr.get_lbaas_uuid_id(my_id) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getObject', identifier=my_id) self.assertEqual(lb_uuid, uuid) self.assertEqual(lb_id, my_id) @@ -121,7 +121,7 @@ def test_remove_lb_listener(self): self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'deleteLoadBalancerProtocols', args=(uuid, [listener])) - def order_lbaas(self): + def test_order_lbaas(self): datacenter = 'tes01' name = 'test-lb' desc = 'my lb' @@ -129,8 +129,41 @@ def order_lbaas(self): subnet_id = 12345 public = True verify = False + package = [ + { + 'id': 805, + 'keyNake': 'LBAAS', + 'itemPrices': [ + { + 'id': 1, + 'name': 'A test price', + 'locationGroupId': None + }, + { + 'id': 2, + 'name': 'A test price 2', + 'locationGroupId': 123 + } + ] + } + ] + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = package + order_data = { + 'complexType': 'SoftLayer_Container_Product_Order_Network_LoadBalancer_AsAService', + 'name': name, + 'description': desc, + 'location': datacenter, + 'packageId': package[0]['id'], + 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'prices': [{'id': package[0]['itemPrices'][0]['id']}], + 'protocolConfigurations': protocols, + 'subnets': [{'id': subnet_id}], + 'isPublic': public + } self.lb_mgr.order_lbaas(datacenter, name, desc, protocols, subnet_id, public, verify) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', args=(order_data,)) + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') verify = True self.lb_mgr.order_lbaas(datacenter, name, desc, protocols, subnet_id, public, verify) self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder') @@ -143,4 +176,3 @@ def test_cancel_lbaas(self): uuid = 'aa-bb-cc' self.lb_mgr.cancel_lbaas(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) - From 4219b23239cb6b0e07551e496db5c7db403ed71e Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Fri, 23 Aug 2019 18:07:56 -0400 Subject: [PATCH 0138/1582] unit test lodabalancer List.py, pool.py --- tests/CLI/modules/loadbal_tests.py | 121 +++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 tests/CLI/modules/loadbal_tests.py diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py new file mode 100644 index 000000000..26505fcc8 --- /dev/null +++ b/tests/CLI/modules/loadbal_tests.py @@ -0,0 +1,121 @@ +""" + SoftLayer.tests.managers.loadbal_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import mock + +import SoftLayer +from SoftLayer import testing +from SoftLayer.CLI.exceptions import ArgumentError + + +class LoadBalancerTests(testing.TestCase): + + def test_cli_list(self): + result = self.run_command(['loadbal', 'list']) + self.assert_no_fail(result) + + def test_cli_list_failed(self): + mock_item = self.set_mock('SoftLayer_Network_LBaaS_LoadBalancer', + 'getAllObjects') + mock_item.return_value = None + result = self.run_command(['loadbal', 'list']) + self.assert_no_fail(result) + + def test_pool(self): + result = self.run_command(['loadbal', 'pool-add', '1111111', '-f 1000', '-b 220', '-c 100']) + self.assert_no_fail(result) + + def test_pool_sticky(self): + result = self.run_command(['loadbal', 'pool-add', '1111111', '-f 1000', '-b 220', '-s']) + self.assert_no_fail(result) + + def test_pool_1(self): + result = self.run_command(['loadbal', 'pool-add', '1111111', '-f 1000', '-b 220']) + self.assert_no_fail(result) + + def test_pool_uuid(self): + result = self.run_command(['loadbal', 'pool-add', '13d08cd1-5533-47b4-b71c-4b6b9dc10000', + '-f 910', '-b 210', '-c 100']) + self.assert_no_fail(result) + + def test_delete_pool(self): + result = self.run_command(['loadbal', 'pool-del', '111111', 'b3a3fdf7-8c16-4e87-aa73-decff510000']) + self.assert_no_fail(result) + + def test_edit_pool(self): + result = self.run_command(['loadbal', 'pool-edit', '111111', '370a9f12-b3be-47b3-bfa5-8e460010000', '-f 510', + '-b 256', '-c 5']) + self.assert_no_fail(result) + + def test_add_7p(self): + result = self.run_command(['loadbal', 'l7pool-add', '0a2da082-4474-4e16-9f02-4de959210000', '-n test', + '-S 10.175.106.180:265:20', '-s']) + self.assert_no_fail(result) + + def test_add_7p_server(self): + result = self.run_command(['loadbal', 'l7pool-add', '111111', + '-S 10.175.106.180:265:20', '-n test', '-s']) + self.assert_no_fail(result) + + def test_del_7p(self): + result = self.run_command(['loadbal', 'l7pool-del', '123456']) + self.assert_no_fail(result) + + def test_add_7p_server_fail(self): + result = self.run_command(['loadbal', 'l7pool-add', '123456', + '-S 10.175.106.180:265:20:20', '-n test', '-s']) + self.assertIsInstance(result.exception, ArgumentError) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_listener') + def test_pool_fail(self, add_lb_pool): + add_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'pool-add', '123456', '-f 1000', '-b 220', '-c 100']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_listener') + def test_pool_sticky_fail(self, add_lb_pool): + add_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'pool-add', '123456', '-f 1000', '-b 220', '-s']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_listener') + def test_pool_1_fail(self, add_lb_pool): + add_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'pool-add', '123456', '-f 1000', '-b 220']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_listener') + def test_pool_uuid_fail(self, add_lb_pool): + add_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command( + ['loadbal', 'pool-add', '13d08cd1-5533-47b4-b71c-4b6b9dc10000', '-f 910', '-b 210', '-c 100']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.remove_lb_listener') + def test_delete_pool_fail(self, del_lb_pool): + del_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'pool-del', '123456', 'b3a3fdf7-8c16-4e87-aa73-decff510000']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_listener') + def test_edit_pool_fail(self, edit_lb_pool): + edit_lb_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'pool-edit', '815248', '370a9f12-b3be-47b3-bfa5-8e10000c013f', '-f 510', + '-b 256', '-c 5']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_l7_pool') + def test_add_7p_fail(self, add_lb_17_pool): + add_lb_17_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'l7pool-add', '0a2da082-4474-4e16-9f02-4de10009b85', '-n test', + '-S 10.175.106.180:265:20', '-s']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.LoadBalancerManager.del_lb_l7_pool') + def test_del_7p_fail(self, del_lb_17_pool): + del_lb_17_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) + result = self.run_command(['loadbal', 'l7pool-del', '123456']) + self.assert_no_fail(result) From 171d15acbfe6a1d606cdd8cf91ab55b590a02c81 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 26 Aug 2019 09:32:12 -0400 Subject: [PATCH 0139/1582] Fix error H306 --- tests/CLI/modules/loadbal_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 26505fcc8..2e78289c8 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -7,8 +7,8 @@ from mock import mock import SoftLayer -from SoftLayer import testing from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer import testing class LoadBalancerTests(testing.TestCase): From 439b07e890846a285d358af8310226001fdcee72 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 26 Aug 2019 10:28:46 -0400 Subject: [PATCH 0140/1582] add fixture load balancer listener and L7pool --- .../SoftLayer_Network_LBaaS_L7Pool.py | 44 ++++++++++++++++++- .../SoftLayer_Network_LBaaS_Listener.py | 44 ++++++++++++++++++- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py index 2d66fcd5c..0acbc6ae9 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_L7Pool.py @@ -1,2 +1,42 @@ -createL7Pool = {} -deleteObject = {} +createL7Pool = {'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + }} +deleteObject = {'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + }} diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index 5af3844e1..57a2459e8 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -1,3 +1,43 @@ # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ -updateLoadBalancerProtocols = {} -deleteLoadBalancerProtocols = {} +updateLoadBalancerProtocols = {'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + }} +deleteLoadBalancerProtocols = {'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + }} From 8d48042f3b40009956193a331b2d5c562fad0d86 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Aug 2019 11:31:09 -0400 Subject: [PATCH 0141/1582] added try-except blocks --- SoftLayer/CLI/loadbal/health.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/loadbal/health.py b/SoftLayer/CLI/loadbal/health.py index 19826ea98..bc0aac629 100644 --- a/SoftLayer/CLI/loadbal/health.py +++ b/SoftLayer/CLI/loadbal/health.py @@ -4,6 +4,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils @@ -55,9 +56,9 @@ def cli(env, identifier, uuid, interval, retry, timeout, url): for key in clean_template.keys(): check[key] = clean_template[key] - result = mgr.update_lb_health_monitors(lb_uuid, [check]) - - if result: + try: + mgr.update_lb_health_monitors(lb_uuid, [check]) click.secho('Health Check {} updated successfully'.format(uuid), fg='green') - else: - click.secho('ERROR: Failed to update {}'.format(uuid), fg='red') + except SoftLayerAPIError as exception: + click.secho('Failed to update {}'.format(uuid), fg='red') + click.secho("ERROR: {}".format(exception.faultString), fg='red') From 3db7f93d9a57f6a4e27e16ca1a93f8da180ae742 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Aug 2019 11:34:44 -0400 Subject: [PATCH 0142/1582] added required datatypes --- .../SoftLayer_Network_LBaaS_LoadBalancer.py | 138 +++++++++++++++--- 1 file changed, 115 insertions(+), 23 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index d9dcce0ca..12668f5cd 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,29 +1,121 @@ getObject = { - 'accountId': 1234, - 'address': '01-307608-ams01.clb.appdomain.cloud', - 'createDate': '2019-08-12T07:49:43-06:00', - 'id': 1111111, - 'isPublic': 0, - 'locationId': 265592, - 'modifyDate': '2019-08-13T16:26:06-06:00', - 'name': 'dcabero-01', - 'operatingStatus': 'ONLINE', - 'provisioningStatus': 'ACTIVE', - 'type': 0, - 'useSystemPublicIpPool': 1, - 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', - 'listenerCount': 4, - 'memberCount': 1, - 'datacenter': { - 'id': 265592, - 'longName': 'Amsterdam 1', - 'name': 'ams01', - 'statusId': 2 + 'accountId': 1234, + 'address': '01-307608-ams01.clb.appdomain.cloud', + 'createDate': '2019-08-12T07:49:43-06:00', + 'id': 1111111, + 'isPublic': 0, + 'locationId': 265592, + 'modifyDate': '2019-08-13T16:26:06-06:00', + 'name': 'dcabero-01', + 'operatingStatus': 'ONLINE', + 'provisioningStatus': 'ACTIVE', + 'type': 0, + 'useSystemPublicIpPool': 1, + 'uuid': '1a1aa111-4474-4e16-9f02-4de959229b85', + 'listenerCount': 4, + 'memberCount': 1, + 'previousErrorText': 'test', + 'datacenter': { + 'id': 265592, + 'longName': 'Amsterdam 1', + 'name': 'ams01', + 'statusId': 2 + }, + 'healthMonitors': [ + { + 'createDate': '2019-08-20T18:05:09-04:00', + 'interval': 5, + 'maxRetries': 2, + 'modifyDate': '2019-08-20T18:05:18-04:00', + 'monitorType': 'HTTP', + 'provisioningStatus': 'ACTIVE', + 'timeout': 2, + 'urlPath': '/', + 'uuid': 'c11111c1-f5ab-4c15-ba96-d7b95dc7c824' } - } + ], + 'l7Pools': [ + { + 'createDate': '2019-08-19T16:33:37-04:00', + 'id': 222222, + 'loadBalancingAlgorithm': 'ROUNDROBIN', + 'modifyDate': None, + 'name': 'test', + 'protocol': 'HTTP', + 'provisioningStatus': 'ACTIVE', + 'uuid': 'a1111111-c5e7-413f-9f78-84f6c5e1ca04' + } + ], + 'listeners': [ + { + 'defaultPool': { + 'healthMonitor': { + 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c8' + }, + 'protocol': 'HTTP', + 'protocolPort': 256, + 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8f', + } + }, + {'connectionLimit': None, + 'createDate': '2019-08-21T17:19:25-04:00', + 'defaultPool': {'createDate': '2019-08-21T17:19:25-04:00', + 'healthMonitor': {'createDate': '2019-08-21T17:17:04-04:00', + 'id': 859330, + 'interval': 5, + 'maxRetries': 2, + 'modifyDate': '2019-08-21T17:17:15-04:00', + 'monitorType': 'HTTP', + 'provisioningStatus': 'ACTIVE', + 'timeout': 2, + 'urlPath': '/', + 'uuid': '55e00152-75fd-4f94-9263-cb4c6e005f12'}, + 'loadBalancingAlgorithm': 'ROUNDROBIN', + 'members': [{'address': '10.136.4.220', + 'createDate': '2019-08-12T09:49:43-04:00', + 'id': 1023118, + 'modifyDate': '2019-08-12T09:52:54-04:00', + 'provisioningStatus': 'ACTIVE', + 'uuid': 'ba23a166-faa4-4eb2-96e7-ef049d65ce60', + 'weight': 50}], + 'modifyDate': '2019-08-21T17:19:33-04:00', + 'protocol': 'HTTP', + 'protocolPort': 230, + 'provisioningStatus': 'ACTIVE', + 'uuid': '1c5f3ba6-ec7d-4cf8-8815-9bb174224a76'}, + 'id': 889072, + 'l7Policies': [{'action': 'REJECT', + 'createDate': '2019-08-21T18:17:41-04:00', + 'id': 215204, + 'modifyDate': None, + 'name': 'trestst', + 'priority': 1, + 'redirectL7PoolId': None, + 'uuid': 'b8c30aae-3979-49a7-be8c-fb70e43a6b4b'}], + 'modifyDate': '2019-08-22T10:58:02-04:00', + 'protocol': 'HTTP', + 'protocolPort': 110, + 'provisioningStatus': 'ACTIVE', + 'tlsCertificateId': None, + 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} + ], + 'members': [ + { + 'address': '10.0.0.1', + 'createDate': '2019-08-12T09:49:43-04:00', + 'modifyDate': '2019-08-12T09:52:54-04:00', + 'provisioningStatus': 'ACTIVE', + 'uuid': 'ba23a166-faa4-4eb2-96e7-ef049d65ce60', + 'weight': 50 + } + ], + 'sslCiphers': [ + { + 'id': 2, 'name': 'ECDHE-RSA-AES256-GCM-SHA384' + } + ], +} getAllObjects = [getObject] - - getLoadBalancerMemberHealth = [ { 'poolUuid': '1c5f3ba6-ec7d-4cf8-8815-9bb174224a76', From 96753cd7af207a0d2a6fa9aed69b9f610fdfbd63 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Aug 2019 11:36:34 -0400 Subject: [PATCH 0143/1582] added health, detail, members test --- tests/CLI/modules/loadbal_tests.py | 109 +++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/CLI/modules/loadbal_tests.py diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py new file mode 100644 index 000000000..549b28387 --- /dev/null +++ b/tests/CLI/modules/loadbal_tests.py @@ -0,0 +1,109 @@ +""" + SoftLayer.tests.CLI.modules.loadbal + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + :license: MIT, see LICENSE for more details. +""" + +import mock + +from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer import exceptions +from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer +from SoftLayer import testing + + +class LoadBalTests(testing.TestCase): + + @mock.patch('SoftLayer.CLI.loadbal.members.click') + def test_lb_member_add_private(self, click): + lbaas_id = '1111111' + member_ip_address = '10.0.0.1' + result = self.run_command(['loadbal', 'member-add', '--private', '-m', member_ip_address, lbaas_id]) + output = 'Member {} added'.format(member_ip_address) + self.assert_no_fail(result) + click.secho.assert_called_with(output, fg='green') + + @mock.patch('SoftLayer.CLI.loadbal.members.click') + def test_lb_member_add_public(self, click): + lbaas_id = '1111111' + member_ip_address = '10.0.0.1' + result = self.run_command(['loadbal', 'member-add', '--public', '-m', member_ip_address, lbaas_id]) + output = 'Member {} added'.format(member_ip_address) + self.assert_no_fail(result) + click.secho.assert_called_with(output, fg='green') + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_member') + def test_lb_member_add_public_fails(self, add_lb_member): + lbaas_id = '1111111' + member_ip_address = '10.0.0.1' + fault_string = 'publicIpAddress must be a string' + add_lb_member.side_effect = exceptions.SoftLayerAPIError(mock.ANY, fault_string) + result = self.run_command(['loadbal', 'member-add', '--public', '-m', member_ip_address, lbaas_id]) + self.assertIn('This LB requires a Public IP address for its members and none was supplied', + result.output) + self.assertIn("ERROR: {}".format(fault_string), + result.output) + + @mock.patch('SoftLayer.LoadBalancerManager.add_lb_member') + def test_lb_member_add_private_fails(self, add_lb_member): + lbaas_id = '1111111' + member_ip_address = '10.0.0.1' + fault_string = 'privateIpAddress must be a string' + add_lb_member.side_effect = exceptions.SoftLayerAPIError(mock.ANY, fault_string) + result = self.run_command(['loadbal', 'member-add', '--private', '-m', member_ip_address, lbaas_id]) + self.assertIn('This LB requires a Private IP address for its members and none was supplied', + result.output) + self.assertIn("ERROR: {}".format(fault_string), + result.output) + + @mock.patch('SoftLayer.managers.load_balancer.LoadBalancerManager.delete_lb_member') + def test_lb_member_del_fails(self, delete): + lbaas_id = '1111111' + lbaas_member_uuid = "x123x123-123x-123x-123x-123a123b123c" + delete.side_effect = exceptions.SoftLayerAPIError(mock.ANY, mock.ANY) + result = self.run_command(['loadbal', 'member-del', '-m', lbaas_member_uuid, lbaas_id]) + self.assertIn("ERROR:", result.output) + + @mock.patch('SoftLayer.CLI.loadbal.members.click') + def test_lb_member_del(self, click): + lbaas_id = '1111111' + lbaas_member_uuid = "x123x123-123x-123x-123x-123a123b123c" + result = self.run_command(['loadbal', 'member-del', '-m', lbaas_member_uuid, lbaas_id]) + output = 'Member {} removed'.format(lbaas_member_uuid) + self.assert_no_fail(result) + click.secho.assert_called_with(output, fg='green') + + @mock.patch('SoftLayer.CLI.loadbal.health.click') + def test_lb_health_manage(self, click): + lb_id = '1111111' + lb_check_uuid = '222222ab-bbcc-4f32-9b31-1b6d3a1959c8' + result = self.run_command(['loadbal', 'health', lb_id, '--uuid', lb_check_uuid, + '-i', '60', '-r', '10', '-t', '10', '-u', '/']) + self.assert_no_fail(result) + output = 'Health Check {} updated successfully'.format(lb_check_uuid) + click.secho.assert_called_with(output, fg='green') + + def test_lb_health_manage_args_time_fails(self): + result = self.run_command(['lb', 'health', '1111111', '--uuid', '222222ab-bbcc-4f32-9b31-1b6d3a1959c8']) + self.assertIsInstance(result.exception, ArgumentError) + + @mock.patch('SoftLayer.LoadBalancerManager.get_lb') + def test_lb_health_update_tcp_url_fails(self, get_lb): + get_lb.return_value = SoftLayer_Network_LBaaS_LoadBalancer.getObject + get_lb.return_value['listeners'][0]['defaultPool']['protocol'] = 'TCP' + + result = self.run_command(['lb', 'health', '1111111', '--uuid', '222222ab-bbcc-4f32-9b31-1b6d3a1959c8', + '-i', '60', '-r', '10', '-t', '10', '-u', '/']) + self.assertIsInstance(result.exception, ArgumentError) + + @mock.patch('SoftLayer.LoadBalancerManager.update_lb_health_monitors') + def test_lb_health_update_fails(self, update_lb_health_monitors): + update_lb_health_monitors.side_effect = exceptions.SoftLayerAPIError(mock.ANY, mock.ANY) + + result = self.run_command(['lb', 'health', '1111111', '--uuid', '222222ab-bbcc-4f32-9b31-1b6d3a1959c8', + '-i', '60', '-r', '10', '-t', '10', '-u', '/']) + self.assertIn("ERROR:", result.output) + + def test_lb_detail(self): + result = self.run_command(['lb', 'detail', '1111111']) + self.assert_no_fail(result) From 584a142e129316ea0a2e25481eea8850d14fe424 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 26 Aug 2019 16:24:25 -0500 Subject: [PATCH 0144/1582] fixed merge conflict error. accidentally removed a line in merge conflict. --- tests/CLI/modules/loadbal_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 7676950ba..48e4e77f1 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -119,6 +119,7 @@ def test_add_7p_fail(self, add_lb_17_pool): def test_del_7p_fail(self, del_lb_17_pool): del_lb_17_pool.side_effect = SoftLayer.exceptions.SoftLayerAPIError(mock.ANY, mock) result = self.run_command(['loadbal', 'l7pool-del', '123456']) + self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.loadbal.members.click') def test_lb_member_add_private(self, click): From 6df37877877ae75c21a75aef4d881e9aba935bdd Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 28 Aug 2019 12:58:17 -0400 Subject: [PATCH 0145/1582] Unit test for loadbal order, ns-list, ns-detail. --- SoftLayer/fixtures/SoftLayer_Account.py | 28 +++- ...Network_Application_Delivery_Controller.py | 19 +++ .../SoftLayer_Network_LBaaS_LoadBalancer.py | 20 +++ .../fixtures/SoftLayer_Product_Package.py | 123 ++++++++++++++++++ tests/CLI/modules/loadbal_tests.py | 79 +++++++++++ tests/managers/network_tests.py | 2 +- 6 files changed, 268 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 1c8800628..7ef79715d 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -323,8 +323,32 @@ 'subnetType': 'PRIMARY', 'ipAddressCount': 10, 'virtualGuests': [], - 'hardware': [] - }] + 'hardware': [], + "podName": "dal05.pod04", + "networkVlan": { + "accountId": 123, + "id": 2581232, + "modifyDate": "2019-07-17T01:09:51+08:00", + "vlanNumber": 795 + } + }, + { + "gateway": "5.111.11.111", + "id": '111', + "modifyDate": "2018-07-24T17:14:57+08:00", + 'networkIdentifier': '10.0.0.1', + 'ipAddressCount': 10, + 'cidr': '/24', + 'virtualGuests': [], + 'hardware': [], + "networkVlanId": 22222, + "sortOrder": "2", + "subnetType": "SECONDARY_ON_VLAN", + "totalIpAddresses": "8", + "usableIpAddressCount": "5", + "version": 4 + } +] getSshKeys = [{'id': '100', 'label': 'Test 1'}, {'id': '101', 'label': 'Test 2', diff --git a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py index 532968d50..4ba9b7328 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py @@ -17,6 +17,25 @@ 'password': 'aaaaa', 'username': 'root' }, + "primaryIpAddress": "159.122.22.222", + "subnets": [ + { + "broadcastAddress": "", + "cidr": 32, + "gateway": "", + "id": 74222, + "modifyDate": "2016-10-26T23:39:12+08:00", + "netmask": "255.255.255.255", + "networkIdentifier": "159.253.111.111", + "networkVlanId": 3611111, + "sortOrder": "4", + "subnetType": "STATIC_IP_ROUTED", + "totalIpAddresses": "2", + "usableIpAddressCount": "2", + "version": 4, + "addressSpace": "PUBLIC" + } + ], 'type': { 'keyName': 'NETSCALER_VPX', 'name': 'NetScaler VPX' diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 12668f5cd..1047a7ce8 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -116,6 +116,26 @@ ], } getAllObjects = [getObject] + +getLoadBalancer = { + "accountId": 3071234, + "createDate": "2019-08-12T21:49:43+08:00", + "id": 81234, + "isPublic": 0, + "locationId": 265592, + "modifyDate": "2019-08-14T06:26:06+08:00", + "name": "dcabero-01", + "uuid": "0a2da082-4474-4e16-9f02-4de11111", + "datacenter": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + } +} + +cancelLoadBalancer = True + getLoadBalancerMemberHealth = [ { 'poolUuid': '1c5f3ba6-ec7d-4cf8-8815-9bb174224a76', diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 186674282..b131f8916 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -784,6 +784,44 @@ 'sortOrder': 10}], 'subDescription': 'Bare Metal Server', 'unitSize': 1, + "itemPrices": [ + { + "hourlyRecurringFee": ".027", + "id": 205911, + "laborFee": "0", + "locationGroupId": 505, + "item": { + "capacity": "0", + "description": "Load Balancer Uptime", + "id": 10785, + "keyName": "LOAD_BALANCER_UPTIME", + } + }, + { + "hourlyRecurringFee": "0", + "id": 199467, + "laborFee": "0", + "locationGroupId": '', + "recurringFee": "0", + "item": { + "capacity": "0", + "description": "Load Balancer Bandwidth", + "id": 10051, + "keyName": "LOAD_BALANCER_BANDWIDTH", + } + }, + { + "hourlyRecurringFee": ".028", + "id": 205913, + "laborFee": "0", + "locationGroupId": 507, + "item": { + "capacity": "0", + "description": "Load Balancer Uptime", + "id": 10785, + "keyName": "LOAD_BALANCER_UPTIME", + } + }] }] getItems = [ @@ -1144,6 +1182,91 @@ 'quantity': 1 } +itemsLoadbal = [ + { + "capacity": "0", + "description": "Load Balancer as a Service", + "id": 10043, + "keyName": "LOAD_BALANCER_AS_A_SERVICE", + "itemCategory": { + "categoryCode": "load_balancer_as_a_service", + "id": 1116, + "name": "Load Balancer As A Service", + }, + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 199447, + "locationGroupId": '', + "recurringFee": "0", + } + ] + }, + { + "capacity": "0", + "description": "Load Balancer Uptime", + "id": 10785, + "keyName": "LOAD_BALANCER_UPTIME", + "itemCategory": { + "categoryCode": "load_balancer_uptime", + "id": 1119, + "name": "Load Balancer Uptime", + }, + "prices": [ + { + "hourlyRecurringFee": ".028", + "id": 205913, + "locationGroupId": 507, + }]} +] + +regionsLoadbal = [{'description': 'WDC01 - Washington, DC - East Coast U.S.', + 'keyname': 'WASHINGTON_DC', + 'location': {'location': {'id': 37473, + 'longName': 'Washington 1', + 'name': 'wdc01', + "groups": [ + { + "description": "Location Group 4", + "id": 507, + "locationGroupTypeId": 82, + "name": "Location Group 4", + "locationGroupType": { + "name": "PRICING" + } + }, + { + "description": "COS Cross Region - EU", + "id": 1303, + "locationGroupTypeId": 82, + "name": "eu", + "locationGroupType": { + "name": "PRICING" + } + }, + { + "description": "COS Regional Frankfurt", + "id": 1783, + "locationGroupTypeId": 82, + "name": "eu-de", + "locationGroupType": { + "name": "PRICING" + } + } + ] + }}, + 'sortOrder': 10}] + +getAllObjectsLoadbal = [ + { + "id": 805, + "keyName": "LBAAS", + "name": "Load Balancer As A Service (LBaaS)", + "items": itemsLoadbal, + "regions": regionsLoadbal + } +] + getAllObjectsDH = [{ "subDescription": "Dedicated Host", "name": "Dedicated Host", diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 48e4e77f1..99549aa79 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -8,6 +8,7 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer import exceptions +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer import testing @@ -214,3 +215,81 @@ def test_lb_health_update_fails(self, update_lb_health_monitors): def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + + def test_order(self): + result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', + 'labeltest', '--subnet', '759282']) + + self.assert_no_fail(result) + + def test_order_with_frontend(self): + result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', + 'labeltest', '--frontend', 'TCP:80', '--backend', 'TCP:80', '--subnet', '759282']) + + self.assert_no_fail(result) + + def test_order_with_backend(self): + result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', + 'labeltest', '--backend', 'HTTP:80', '--subnet', '759282']) + + self.assert_no_fail(result) + + def test_order_backend_fail(self): + result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', + 'labeltest', '--backend', 'HTTP', '--subnet', '759282']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, ArgumentError) + + def test_verify_order(self): + result = self.run_command(['loadbal', 'order', '--verify', '--name', 'test', '--datacenter', 'par01', '--label', + 'labeltest', '--subnet', '759282']) + + self.assert_no_fail(result) + + def test_order_options(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = SoftLayer_Product_Package.getAllObjectsLoadbal + result = self.run_command(['loadbal', 'order-options']) + + self.assert_no_fail(result) + + def test_order_options_with_datacenter(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = SoftLayer_Product_Package.getAllObjectsLoadbal + result = self.run_command(['loadbal', 'order-options', '--datacenter', 'ams03']) + + self.assert_no_fail(result) + + def test_cancel(self): + result = self.run_command(['loadbal', 'cancel', '11111']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer') + + @mock.patch('SoftLayer.LoadBalancerManager.cancel_lbaas') + def test_cancel_fail(self, cancel_lbaas): + fault_string = 'Id must be string' + cancel_lbaas.side_effect = exceptions.SoftLayerAPIError(mock.ANY, fault_string) + result = self.run_command(['loadbal', 'cancel', '11111']) + + self.assertIn("ERROR: {}".format(fault_string), + result.output) + + def test_ns_list(self): + result = self.run_command(['loadbal', 'ns-list']) + + self.assert_no_fail(result) + + def test_ns_list_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getApplicationDeliveryControllers') + mock.return_value = [] + + result = self.run_command(['loadbal', 'ns-list']) + + self.assert_no_fail(result) + + def test_ns_detail(self): + result = self.run_command(['loadbal', 'ns-detail', '11111']) + + self.assert_no_fail(result) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index a87441a99..fd986e2ea 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -435,7 +435,7 @@ def test_resolve_global_ip_ids_no_results(self): def test_resolve_subnet_ids(self): _id = self.network.resolve_subnet_ids('10.0.0.1/29') - self.assertEqual(_id, ['100']) + self.assertEqual(_id, ['100', '111']) def test_resolve_subnet_ids_no_results(self): mock = self.set_mock('SoftLayer_Account', 'getSubnets') From de382b3116066438fd8c8d1477718ebe953429d1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 28 Aug 2019 13:27:24 -0400 Subject: [PATCH 0146/1582] Fix file analysis. --- tests/CLI/modules/loadbal_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 99549aa79..b87dab6e9 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -8,8 +8,8 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer import exceptions -from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing From 745ec85bca1ab7061cafde8488ed4bb18f9586ed Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 28 Aug 2019 15:12:34 -0400 Subject: [PATCH 0147/1582] Fix file analysis. --- ..._Network_Application_Delivery_Controller.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py index 4ba9b7328..5f29c915f 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Application_Delivery_Controller.py @@ -13,11 +13,27 @@ 'longName': 'Dallas 9', 'name': 'dal09', }, + "networkVlans": [ + { + "accountId": 11111, + "id": 33333, + "modifyDate": "2019-07-17T01:09:38+08:00", + "name": "FirewallTesting", + "primarySubnetId": 91111, + "vlanNumber": 1711 + }, + { + "accountId": 11111, + "id": 862222, + "modifyDate": "2019-07-17T01:09:42+08:00", + "primarySubnetId": 502211, + "vlanNumber": 722 + } + ], 'password': { 'password': 'aaaaa', 'username': 'root' }, - "primaryIpAddress": "159.122.22.222", "subnets": [ { "broadcastAddress": "", From 8448ccd729cf19649eaf918c66078b655f74c866 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 28 Aug 2019 14:23:04 -0500 Subject: [PATCH 0148/1582] #1158 added LB docs --- SoftLayer/managers/cdn.py | 10 ++--- SoftLayer/managers/load_balancer.py | 11 +++-- docs/cli/loadbal.rst | 62 +++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 docs/cli/loadbal.rst diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 51dfb5252..95a32f870 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -74,11 +74,11 @@ def add_origin(self, unique_id, origin, path, origin_type="server", header=None, :param str protocol: the protocol of the origin (default: HTTP) :param str bucket_name: name of the available resource :param str file_extensions: file extensions that can be stored in the CDN, e.g. "jpg,png" - :param str optimize_for: performance configuration, available options: web, video, and file - where: - 'web' --> 'General web delivery' - 'video' --> 'Video on demand optimization' - 'file' --> 'Large file optimization' + :param str optimize_for: performance configuration, available options: web, video, and file where: + + - 'web' = 'General web delivery' + - 'video' = 'Video on demand optimization' + - 'file' = 'Large file optimization' :param str cache_query: rules with the following formats: 'include-all', 'ignore-all', 'include: space separated query-names', 'ignore: space separated query-names'.' diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 072077ef0..ea67e469c 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -76,8 +76,11 @@ def get_lb(self, identifier, mask=None): def update_lb_health_monitors(self, uuid, checks): """calls SoftLayer_Network_LBaaS_HealthMonitor::updateLoadBalancerHealthMonitors() - https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_HealthMonitor/updateLoadBalancerHealthMonitors/ - https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration/ + - `updateLoadBalancerHealthMonitors `_ + - `SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration `_ + :param uuid: loadBalancerUuid :param checks list: SoftLayer_Network_LBaaS_LoadBalancerHealthMonitorConfiguration[] """ @@ -137,8 +140,8 @@ def add_lb_listener(self, identifier, listener): def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance - https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_L7Pool/createL7Pool/ - https://cloud.ibm.com/docs/infrastructure/loadbalancer-service?topic=loadbalancer-service-api-reference + - https://sldn.softlayer.com/reference/services/SoftLayer_Network_LBaaS_L7Pool/createL7Pool/ + - https://cloud.ibm.com/docs/infrastructure/loadbalancer-service?topic=loadbalancer-service-api-reference :param identifier: UUID of the LBaaS instance :param pool SoftLayer_Network_LBaaS_L7Pool: Description of the pool diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst new file mode 100644 index 000000000..b1a6a0dcc --- /dev/null +++ b/docs/cli/loadbal.rst @@ -0,0 +1,62 @@ +.. _cli_loadbalancer: + +LoadBalancers +=================================== +These commands were added in version `5.8.0 `_ + +LBaaS Commands +~~~~~~~~~~~~~~ + +- `LBaaS Product `_ +- `LBaaS Documentation `_ + +.. click:: SoftLayer.CLI.loadbal.detail:cli + :prog: loadbal detail + :show-nested: +.. click:: SoftLayer.CLI.loadbal.list:cli + :prog: loadbal list + :show-nested: +.. click:: SoftLayer.CLI.loadbal.health:cli + :prog: loadbal health + :show-nested: +.. click:: SoftLayer.CLI.loadbal.members:add + :prog: loadbal member-add + :show-nested: +.. click:: SoftLayer.CLI.loadbal.members:remove + :prog: loadbal member-remote + :show-nested: +.. click:: SoftLayer.CLI.loadbal.pools:add + :prog: loadbal pool-add + :show-nested: +.. click:: SoftLayer.CLI.loadbal.pools:edit + :prog: loadbal pool-edit + :show-nested: +.. click:: SoftLayer.CLI.loadbal.pools:delete + :prog: loadbal pool-delete + :show-nested: +.. click:: SoftLayer.CLI.loadbal.pools:l7pool_add + :prog: loadbal l7pool-add + :show-nested: +.. click:: SoftLayer.CLI.loadbal.pools:l7pool_del + :prog: loadbal l7pool-del + :show-nested: +.. click:: SoftLayer.CLI.loadbal.order:order + :prog: loadbal order + :show-nested: +.. click:: SoftLayer.CLI.loadbal.order:order_options + :prog: loadbal order-options + :show-nested: +.. click:: SoftLayer.CLI.loadbal.order:cancel + :prog: loadbal cancel + :show-nested: + + +NetScaler Commands +~~~~~~~~~~~~~~~~~~ + +.. click:: SoftLayer.CLI.loadbal.ns_detail:cli + :prog: loadbal ns-detail + :show-nested: +.. click:: SoftLayer.CLI.loadbal.ns_list:cli + :prog: loadbal ns-list + :show-nested: \ No newline at end of file From d7d68d763ca022918f870d310c17fc0ffa7d54af Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 4 Sep 2019 15:06:14 -0500 Subject: [PATCH 0149/1582] Version to 5.8.0 --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf8c1149..a28f161d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,42 @@ # Change Log + +## [5.8.0] - 2019-09-04 +- https://github.com/softlayer/softlayer-python/compare/v5.7.2...v5.8.0 + ++ #1143 Upgrade to prompt_toolkit >= 2 ++ #1003 Bandwidth Feature + * slcli summary + * slcli report bandwidth + * slcli vs bandwidth + * slcli hw bandwidth + * Added bandwidth to VS and HW details page ++ #1146 DOCS: replace 'developer' with 'sldn' links ++ #1147 property 'contents' is not valid for 'SoftLayer_Ticket' when creating a ticket ++ #1139 cannot create static subnet with slcli ++ #1145 Refactor cdn network. ++ #1152 IBMID auth support ++ #1153, #1052 Transient VSI support ++ #1167 Removed legacy LoadBalancer command, added Citrix and IBM LBaaS commands. + * slcli lb cancel + * slcli lb detail + * slcli lb health + * slcli lb l7pool-add + * slcli lb l7pool-del + * slcli lb list + * slcli lb member-add + * slcli lb member-del + * slcli lb ns-detail + * slcli lb ns-list + * slcli lb order + * slcli lb order-options + * slcli lb pool-add + * slcli lb pool-del + * slcli lb pool-edit ++ #1157 Remove VpnAllowedFlag. ++ #1160 Improve hardware cancellation to deal with additional cases + ## [5.7.2] - 2019-05-03 - https://github.com/softlayer/softlayer-python/compare/v5.7.1...v5.7.2 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index a9927d986..1171bd578 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.7.2' +VERSION = 'v5.8.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 692ef789d..dc6b7514b 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.7.2', + version='5.8.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d6634a551..a2190f100 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.7.2+git' # check versioning +version: '5.8.0+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From aab9ca0eeba65ce1a40116b6413b7d852c6fab8c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 4 Sep 2019 15:22:52 -0500 Subject: [PATCH 0150/1582] fixed type in readme.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 1f8fb872a..51ea9074a 100644 --- a/README.rst +++ b/README.rst @@ -94,7 +94,7 @@ For the CLI, just use the -vvv option. If you are using the REST endpoint, this If you are using the library directly in python, you can do something like this. -.. code-bock:: python +.. code-block:: python import SoftLayer import logging From f9a0be9eb5af55b309987297d8a4884508f1c3ad Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 9 Sep 2019 22:39:30 -0500 Subject: [PATCH 0151/1582] Drop python2.7 support. Remove six and extraneous __future__ imports. Update dependencies and setup. --- .travis.yml | 2 +- README.rst | 3 +-- SoftLayer/API.py | 1 - SoftLayer/CLI/config/setup.py | 6 +++--- SoftLayer/CLI/core.py | 1 - SoftLayer/CLI/deprecated.py | 1 - SoftLayer/CLI/formatting.py | 2 +- SoftLayer/CLI/report/bandwidth.py | 1 - SoftLayer/CLI/template.py | 8 ++++---- SoftLayer/config.py | 5 ++--- SoftLayer/shell/core.py | 1 - SoftLayer/testing/__init__.py | 3 +-- SoftLayer/testing/xmlrpc.py | 26 +++++++++++++------------- SoftLayer/transports.py | 11 ++++++----- SoftLayer/utils.py | 9 +-------- setup.py | 6 ++---- tests/CLI/core_tests.py | 14 +++++++------- tests/CLI/deprecated_tests.py | 7 ++++--- tests/CLI/helper_tests.py | 9 --------- tests/CLI/modules/call_api_tests.py | 6 +----- tests/config_tests.py | 6 +++--- tests/transport_tests.py | 5 ++--- tools/requirements.txt | 1 - tools/test-requirements.txt | 1 - tox.ini | 2 +- 25 files changed, 53 insertions(+), 84 deletions(-) diff --git a/.travis.yml b/.travis.yml index d69b42d68..d3cc13a76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: - python: "3.7" env: TOX_ENV=py37 - python: "pypy3.5" - env: TOX_ENV=pypy + env: TOX_ENV=pypy3 - python: "3.6" env: TOX_ENV=analysis - python: "3.6" diff --git a/README.rst b/README.rst index 1f8fb872a..53f5c2b53 100644 --- a/README.rst +++ b/README.rst @@ -124,14 +124,13 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 2.7, 3.3, 3.4, 3.5, 3.6, or 3.7. +* Python 3.5, 3.6, or 3.7. * A valid SoftLayer API username and key. * A connection to SoftLayer's private network is required to use our private network API endpoints. Python Packages --------------- -* six >= 1.7.0 * ptable >= 0.9.2 * click >= 7 * requests >= 2.20.0 diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e65da3884..b20b13aaa 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,7 +6,6 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name -from __future__ import generators import warnings diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index c984d569e..9b1259891 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,5 +1,6 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. +import configparser import os.path import click @@ -9,7 +10,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import utils def get_api_key(client, username, secret): @@ -65,11 +65,11 @@ def cli(env): # Persist the config file. Read the target config file in before # setting the values to avoid clobbering settings - parsed_config = utils.configparser.RawConfigParser() + parsed_config = configparser.RawConfigParser() parsed_config.read(config_path) try: parsed_config.add_section('softlayer') - except utils.configparser.DuplicateSectionError: + except configparser.DuplicateSectionError: pass parsed_config.set('softlayer', 'username', username) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index a05ffaa54..6b4ab007b 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import print_function import logging import os import sys diff --git a/SoftLayer/CLI/deprecated.py b/SoftLayer/CLI/deprecated.py index d4c1d3140..3b1da4b6c 100644 --- a/SoftLayer/CLI/deprecated.py +++ b/SoftLayer/CLI/deprecated.py @@ -4,7 +4,6 @@ Handles usage of the deprecated command name, 'sl'. :license: MIT, see LICENSE for more details. """ -from __future__ import print_function import sys diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 48e271335..b591f814f 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -30,7 +30,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 SequentialOutput :param string fmt (optional): One of: table, raw, json, python """ - if isinstance(data, utils.string_types): + if isinstance(data, str): if fmt in ('json', 'jsonraw'): return json.dumps(data) return data diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index f7f28e00c..e2b15d981 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -1,5 +1,4 @@ """Metric Utilities""" -from __future__ import print_function import datetime import itertools import sys diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 4b03fce3f..b68ed6554 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -9,10 +9,10 @@ """ # pylint: disable=redefined-argument-from-local +import configparser +import io import os.path -from SoftLayer import utils - class TemplateCallback(object): """Callback to use to populate click arguments with a template.""" @@ -24,10 +24,10 @@ def __call__(self, ctx, param, value): if value is None: return - config = utils.configparser.ConfigParser() + config = configparser.ConfigParser() ini_str = '[settings]\n' + open( os.path.expanduser(value), 'r').read() - ini_fp = utils.StringIO(ini_str) + ini_fp = io.StringIO(ini_str) config.readfp(ini_fp) # Merge template options with the options passed in diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 6301ff3a0..d008893f0 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -5,11 +5,10 @@ :license: MIT, see LICENSE for more details. """ +import configparser import os import os.path -from SoftLayer import utils - def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -51,7 +50,7 @@ def get_client_settings_config_file(**kwargs): # pylint: disable=inconsistent-r if kwargs.get('config_file'): config_files.append(kwargs.get('config_file')) config_files = [os.path.expanduser(f) for f in config_files] - config = utils.configparser.RawConfigParser({ + config = configparser.RawConfigParser({ 'username': '', 'api_key': '', 'endpoint_url': '', diff --git a/SoftLayer/shell/core.py b/SoftLayer/shell/core.py index 55a56e888..8946815e2 100644 --- a/SoftLayer/shell/core.py +++ b/SoftLayer/shell/core.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -from __future__ import print_function import copy import os import shlex diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 87d7f5e41..a2caa8888 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -6,7 +6,6 @@ """ # Disable pylint import error and too many methods error # pylint: disable=invalid-name -from __future__ import print_function import logging import os.path @@ -79,7 +78,7 @@ def _mock_key(service, method): class TestCase(testtools.TestCase): - """Testcase class with PEP-8 compatable method names.""" + """Testcase class with PEP-8 compatible method names.""" @classmethod def setUpClass(cls): diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py index bd74afe93..a38e85eeb 100644 --- a/SoftLayer/testing/xmlrpc.py +++ b/SoftLayer/testing/xmlrpc.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ +import http.server import logging import threading - -import six +import xmlrpc.client import SoftLayer from SoftLayer import transports @@ -17,15 +17,15 @@ # pylint: disable=invalid-name, broad-except, arguments-differ -class TestServer(six.moves.BaseHTTPServer.HTTPServer): +class TestServer(http.server.HTTPServer): """Test HTTP server which holds a given transport.""" def __init__(self, transport, *args, **kw): - six.moves.BaseHTTPServer.HTTPServer.__init__(self, *args, **kw) + http.server.HTTPServer.__init__(self, *args, **kw) self.transport = transport -class TestHandler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler): +class TestHandler(http.server.BaseHTTPRequestHandler): """Test XML-RPC Handler which converts XML-RPC to transport requests.""" def do_POST(self): @@ -33,7 +33,7 @@ def do_POST(self): try: length = int(self.headers['Content-Length']) data = self.rfile.read(length).decode('utf-8') - args, method = utils.xmlrpc_client.loads(data) + args, method = xmlrpc.client.loads(data) headers = args[0].get('headers', {}) # Form Request for the transport @@ -54,9 +54,9 @@ def do_POST(self): # Get response response = self.server.transport(req) - response_body = utils.xmlrpc_client.dumps((response,), - allow_none=True, - methodresponse=True) + response_body = xmlrpc.client.dumps((response,), + allow_none=True, + methodresponse=True) self.send_response(200) self.send_header("Content-type", "application/xml; charset=UTF-8") @@ -69,10 +69,10 @@ def do_POST(self): except SoftLayer.SoftLayerAPIError as ex: self.send_response(200) self.end_headers() - response = utils.xmlrpc_client.Fault(ex.faultCode, str(ex.reason)) - response_body = utils.xmlrpc_client.dumps(response, - allow_none=True, - methodresponse=True) + response = xmlrpc.client.Fault(ex.faultCode, str(ex.reason)) + response_body = xmlrpc.client.dumps(response, + allow_none=True, + methodresponse=True) self.wfile.write(response_body.encode('utf-8')) except Exception as ex: self.send_response(500) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index b4790c60e..8849a5c2f 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -10,6 +10,7 @@ import logging import re import time +import xmlrpc.client import requests from requests.adapters import HTTPAdapter @@ -200,9 +201,9 @@ def __call__(self, request): request.transport_headers.setdefault('User-Agent', self.user_agent) request.url = '/'.join([self.endpoint_url, request.service]) - request.payload = utils.xmlrpc_client.dumps(tuple(largs), - methodname=request.method, - allow_none=True) + request.payload = xmlrpc.client.dumps(tuple(largs), + methodname=request.method, + allow_none=True) # Prefer the request setting, if it's not None verify = request.verify @@ -220,13 +221,13 @@ def __call__(self, request): proxies=_proxies_dict(self.proxy)) resp.raise_for_status() - result = utils.xmlrpc_client.loads(resp.content)[0][0] + 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 utils.xmlrpc_client.Fault as ex: + 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 = { diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 21138e6ae..74ad84b96 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -9,18 +9,11 @@ import re import time -import six - # pylint: disable=no-member, invalid-name UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '!~', '*=', '^=', '$=', '_='] -configparser = six.moves.configparser -string_types = six.string_types -StringIO = six.StringIO -xmlrpc_client = six.moves.xmlrpc_client - def lookup(dic, key, *keys): """A generic dictionary access helper. @@ -91,7 +84,7 @@ def query_filter(query): except ValueError: pass - if isinstance(query, string_types): + if isinstance(query, str): query = query.strip() for operation in KNOWN_OPERATIONS: if query.startswith(operation): diff --git a/setup.py b/setup.py index dc6b7514b..cb4e93b5e 100644 --- a/setup.py +++ b/setup.py @@ -29,8 +29,8 @@ 'sl = SoftLayer.CLI.deprecated:main', ], }, + python_requires='>=3.3', install_requires=[ - 'six >= 1.7.0', 'ptable >= 0.9.2', 'click >= 7', 'requests >= 2.20.0', @@ -48,11 +48,9 @@ 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index bf5879c45..321f78b1e 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -4,16 +4,16 @@ :license: MIT, see LICENSE for more details. """ +import io import logging +import click +import mock + import SoftLayer from SoftLayer.CLI import core from SoftLayer.CLI import environment from SoftLayer import testing -from SoftLayer import utils - -import click -import mock class CoreTests(testing.TestCase): @@ -56,7 +56,7 @@ def test_diagnostics(self): class CoreMainTests(testing.TestCase): @mock.patch('SoftLayer.CLI.core.cli.main') - @mock.patch('sys.stdout', new_callable=utils.StringIO) + @mock.patch('sys.stdout', new_callable=io.StringIO) def test_unexpected_error(self, stdoutmock, climock): climock.side_effect = AttributeError('Attribute foo does not exist') @@ -70,7 +70,7 @@ def test_unexpected_error(self, stdoutmock, climock): stdoutmock.getvalue()) @mock.patch('SoftLayer.CLI.core.cli.main') - @mock.patch('sys.stdout', new_callable=utils.StringIO) + @mock.patch('sys.stdout', new_callable=io.StringIO) def test_sl_error(self, stdoutmock, climock): ex = SoftLayer.SoftLayerAPIError('SoftLayer_Exception', 'Not found') climock.side_effect = ex @@ -81,7 +81,7 @@ def test_sl_error(self, stdoutmock, climock): stdoutmock.getvalue()) @mock.patch('SoftLayer.CLI.core.cli.main') - @mock.patch('sys.stdout', new_callable=utils.StringIO) + @mock.patch('sys.stdout', new_callable=io.StringIO) def test_auth_error(self, stdoutmock, climock): ex = SoftLayer.SoftLayerAPIError('SoftLayer_Exception', 'Invalid API token.') diff --git a/tests/CLI/deprecated_tests.py b/tests/CLI/deprecated_tests.py index ddb3ea350..f28025f36 100644 --- a/tests/CLI/deprecated_tests.py +++ b/tests/CLI/deprecated_tests.py @@ -4,18 +4,19 @@ :license: MIT, see LICENSE for more details. """ +import io + import mock from SoftLayer.CLI import deprecated from SoftLayer import testing -from SoftLayer import utils class EnvironmentTests(testing.TestCase): def test_main(self): - with mock.patch('sys.stderr', new=utils.StringIO()) as fake_out: + with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: ex = self.assertRaises(SystemExit, deprecated.main) self.assertEqual(ex.code, -1) @@ -23,7 +24,7 @@ def test_main(self): fake_out.getvalue()) def test_with_args(self): - with mock.patch('sys.stderr', new=utils.StringIO()) as fake_out: + with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: with mock.patch('sys.argv', new=['sl', 'module', 'subcommand']): ex = self.assertRaises(SystemExit, deprecated.main) self.assertEqual(ex.code, -1) diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 40752afae..6da71c7d2 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -10,10 +10,8 @@ import sys import tempfile - import click import mock -import six from SoftLayer.CLI import core from SoftLayer.CLI import exceptions @@ -98,13 +96,6 @@ def test_init(self): self.assertEqual('test', item.formatted) self.assertEqual('test', str(item)) - def test_unicode(self): - if six.PY2: - item = formatting.FormattedItem(u'\u32423', u'\u32423') - self.assertEqual(u'\u32423', item.original) - self.assertEqual(u'\u32423', item.formatted) - self.assertEqual('invalid', str(item)) - def test_mb_to_gb(self): item = formatting.mb_to_gb(1024) self.assertEqual(1024, item.original) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index b907d200c..123528607 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -94,11 +94,7 @@ def test_python_output(self): '--output-python']) self.assert_no_fail(result) - # NOTE(kmcdonald): Python 3 no longer inserts 'u' before unicode - # string literals but python 2 does. These are stripped out to make - # this test pass on both python versions. - stripped_output = result.output.replace("u'", "'") - self.assertIsNotNone(stripped_output, """import SoftLayer + self.assertIsNotNone(result.output, """import SoftLayer client = SoftLayer.create_client_from_env() result = client.call(u'Service', diff --git a/tests/config_tests.py b/tests/config_tests.py index 4224bb7b2..f6adb1be6 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -69,7 +69,7 @@ def test_username_api_key(self): class TestGetClientSettingsConfigFile(testing.TestCase): - @mock.patch('six.moves.configparser.RawConfigParser') + @mock.patch('configparser.RawConfigParser') def test_username_api_key(self, config_parser): result = config.get_client_settings_config_file() @@ -79,7 +79,7 @@ def test_username_api_key(self, config_parser): self.assertEqual(result['username'], config_parser().get()) self.assertEqual(result['api_key'], config_parser().get()) - @mock.patch('six.moves.configparser.RawConfigParser') + @mock.patch('configparser.RawConfigParser') def test_no_section(self, config_parser): config_parser().has_section.return_value = False result = config.get_client_settings_config_file() @@ -87,7 +87,7 @@ def test_no_section(self, config_parser): self.assertIsNone(result) -@mock.patch('six.moves.configparser.RawConfigParser') +@mock.patch('configparser.RawConfigParser') def test_config_file(config_parser): config.get_client_settings_config_file(config_file='path/to/config') config_parser().read.assert_called_with([mock.ANY, diff --git a/tests/transport_tests.py b/tests/transport_tests.py index e7a71a6fa..d105c3fdc 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -10,7 +10,6 @@ import mock import pytest import requests -import six import SoftLayer from SoftLayer import consts @@ -20,7 +19,7 @@ def get_xmlrpc_response(): response = requests.Response() - list_body = six.b(''' + list_body = b''' @@ -29,7 +28,7 @@ def get_xmlrpc_response(): -''') +''' response.raw = io.BytesIO(list_body) response.headers['SoftLayer-Total-Items'] = 10 response.status_code = 200 diff --git a/tools/requirements.txt b/tools/requirements.txt index 0d7746444..ad902bc39 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,4 +1,3 @@ -six >= 1.7.0 ptable >= 0.9.2 click >= 7 requests >= 2.20.0 diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index 2869de5e6..3080abf43 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,7 +4,6 @@ pytest-cov mock sphinx testtools -six >= 1.7.0 ptable >= 0.9.2 click >= 7 requests >= 2.20.0 diff --git a/tox.ini b/tox.ini index ff08bac17..b0d521ac9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,pypy,analysis,coverage +envlist = py35,py36,py37,pypy3,analysis,coverage [flake8] From b2b4e26154d6f1f1f0408ef523f92c8e6ea8eaa2 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 9 Sep 2019 22:49:43 -0500 Subject: [PATCH 0152/1582] Remove ConfigParser use of readfp for read_file. --- SoftLayer/CLI/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index b68ed6554..437978f78 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -28,7 +28,7 @@ def __call__(self, ctx, param, value): ini_str = '[settings]\n' + open( os.path.expanduser(value), 'r').read() ini_fp = io.StringIO(ini_str) - config.readfp(ini_fp) + config.read_file(ini_fp) # Merge template options with the options passed in args = {} From 78f196f857d0a16a0d0179479ea9c17bb78c36a1 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 9 Sep 2019 22:54:42 -0500 Subject: [PATCH 0153/1582] Correct python_requires to a minimum of python 3.5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cb4e93b5e..ae0e85e41 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ 'sl = SoftLayer.CLI.deprecated:main', ], }, - python_requires='>=3.3', + python_requires='>=3.5', install_requires=[ 'ptable >= 0.9.2', 'click >= 7', From 6dae28c790c94e444561ceed9759e63e9749ce9f Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Fri, 20 Sep 2019 14:32:52 -0400 Subject: [PATCH 0154/1582] add the new services id --- SoftLayer/CLI/ticket/__init__.py | 1 + SoftLayer/CLI/ticket/list.py | 3 ++- SoftLayer/fixtures/SoftLayer_Account.py | 3 +++ SoftLayer/fixtures/SoftLayer_Ticket.py | 4 ++++ SoftLayer/managers/ticket.py | 5 ++--- tests/CLI/modules/ticket_tests.py | 5 ++++- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index 11fe2a879..9886aa686 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -32,6 +32,7 @@ def get_ticket_results(mgr, ticket_id, update_count=1): table.align['value'] = 'l' table.add_row(['id', ticket['id']]) + table.add_row(['Case_Number', ticket['serviceProviderResourceId']]) table.add_row(['title', ticket['title']]) table.add_row(['priority', PRIORITY_MAP[ticket.get('priority', 0)]]) if ticket.get('assignedUser'): diff --git a/SoftLayer/CLI/ticket/list.py b/SoftLayer/CLI/ticket/list.py index 64c8b7dd6..c123b8234 100644 --- a/SoftLayer/CLI/ticket/list.py +++ b/SoftLayer/CLI/ticket/list.py @@ -16,7 +16,7 @@ def cli(env, is_open): """List tickets.""" ticket_mgr = SoftLayer.TicketManager(env.client) table = formatting.Table([ - 'id', 'assigned_user', 'title', 'last_edited', 'status', 'updates', 'priority' + 'id', 'Case_Number', 'assigned_user', 'title', 'last_edited', 'status', 'updates', 'priority' ]) tickets = ticket_mgr.list_tickets(open_status=is_open, closed_status=not is_open) @@ -27,6 +27,7 @@ def cli(env, is_open): table.add_row([ ticket['id'], + ticket['serviceProviderResourceId'], user, click.wrap_text(ticket['title']), ticket['lastEditDate'], diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index cf884aefd..4239b9b5d 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -340,6 +340,7 @@ getTickets = [ { "accountId": 1234, + "serviceProviderResourceId": "CS123456", "assignedUserId": 12345, "createDate": "2013-08-01T14:14:04-07:00", "id": 100, @@ -355,6 +356,7 @@ }, { "accountId": 1234, + "serviceProviderResourceId": "CS123456", "assignedUserId": 12345, "createDate": "2013-08-01T14:14:04-07:00", "id": 101, @@ -370,6 +372,7 @@ }, { "accountId": 1234, + "serviceProviderResourceId": "CS123456", "assignedUserId": 12345, "createDate": "2014-03-03T09:44:01-08:00", "id": 102, diff --git a/SoftLayer/fixtures/SoftLayer_Ticket.py b/SoftLayer/fixtures/SoftLayer_Ticket.py index ea230f98e..b84031f55 100644 --- a/SoftLayer/fixtures/SoftLayer_Ticket.py +++ b/SoftLayer/fixtures/SoftLayer_Ticket.py @@ -1,6 +1,7 @@ createCancelServerTicket = {'id': 1234, 'title': 'Server Cancellation Request'} getObject = { "accountId": 1234, + "serviceProviderResourceId": "CS123456", "assignedUserId": 12345, "createDate": "2013-08-01T14:14:04-07:00", "id": 100, @@ -26,6 +27,7 @@ createStandardTicket = { "assignedUserId": 12345, + "serviceProviderResourceId": "CS123456", "id": 100, "contents": "body", "subjectId": 1004, @@ -34,6 +36,8 @@ edit = True addUpdate = {} +list = getObject + addAttachedHardware = { "id": 123, "createDate": "2013-08-01T14:14:04-07:00", diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py index 04f8470b0..9ff361d4b 100644 --- a/SoftLayer/managers/ticket.py +++ b/SoftLayer/managers/ticket.py @@ -28,7 +28,7 @@ def list_tickets(self, open_status=True, closed_status=True): :param boolean open_status: include open tickets :param boolean closed_status: include closed tickets """ - mask = """mask[id, title, assignedUser[firstName, lastName], priority, + mask = """mask[id, serviceProviderResourceId, title, assignedUser[firstName, lastName], priority, createDate, lastEditDate, accountId, status, updateCount]""" call = 'getTickets' @@ -39,7 +39,6 @@ def list_tickets(self, open_status=True, closed_status=True): call = 'getClosedTickets' else: raise ValueError("open_status and closed_status cannot both be False") - return self.client.call('Account', call, mask=mask, iter=True) def list_subjects(self): @@ -53,7 +52,7 @@ def get_ticket(self, ticket_id): :returns: dict -- information about the specified ticket """ - mask = """mask[id, title, assignedUser[firstName, lastName],status, + mask = """mask[id, serviceProviderResourceId, title, assignedUser[firstName, lastName],status, createDate,lastEditDate,updates[entry,editor],updateCount, priority]""" return self.ticket.getObject(id=ticket_id, mask=mask) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 3f338cf1c..4acf56965 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -21,6 +21,7 @@ def test_list(self): expected = [{ 'assigned_user': 'John Smith', + 'Case_Number': 'CS123456', 'id': 102, 'last_edited': '2013-08-01T14:16:47-07:00', 'priority': 0, @@ -34,6 +35,7 @@ def test_detail(self): result = self.run_command(['ticket', 'detail', '1']) expected = { + 'Case_Number': 'CS123456', 'created': '2013-08-01T14:14:04-07:00', 'edited': '2013-08-01T14:16:47-07:00', 'id': 100, @@ -235,6 +237,7 @@ def test_init_ticket_results(self): def test_init_ticket_results_asigned_user(self): mock = self.set_mock('SoftLayer_Ticket', 'getObject') mock.return_value = { + "serviceProviderResourceId": "CS12345", "id": 100, "title": "Simple Title", "priority": 1, @@ -296,4 +299,4 @@ def test_ticket_update_no_body(self, edit_mock): edit_mock.return_value = 'Testing1' result = self.run_command(['ticket', 'update', '100']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) \ No newline at end of file From aa445e9d50fe01242ca1e80c1078b4345af97cc5 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Fri, 20 Sep 2019 14:57:10 -0400 Subject: [PATCH 0155/1582] add the line blank --- tests/CLI/modules/ticket_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 4acf56965..6422ce3d6 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -299,4 +299,5 @@ def test_ticket_update_no_body(self, edit_mock): edit_mock.return_value = 'Testing1' result = self.run_command(['ticket', 'update', '100']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) \ No newline at end of file + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + \ No newline at end of file From 4e8d17c594e67d961c400f312ed28e404f110a4a Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Fri, 20 Sep 2019 15:06:50 -0400 Subject: [PATCH 0156/1582] fix identation and errors --- tests/CLI/modules/ticket_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 6422ce3d6..2a47e782e 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -300,4 +300,3 @@ def test_ticket_update_no_body(self, edit_mock): result = self.run_command(['ticket', 'update', '100']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) - \ No newline at end of file From 93cda54139ca89e4a3a70bd071813e1bd57374f9 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Fri, 20 Sep 2019 15:22:38 -0400 Subject: [PATCH 0157/1582] fix identation and errors in fixtures --- SoftLayer/fixtures/SoftLayer_Ticket.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Ticket.py b/SoftLayer/fixtures/SoftLayer_Ticket.py index b84031f55..ece702f28 100644 --- a/SoftLayer/fixtures/SoftLayer_Ticket.py +++ b/SoftLayer/fixtures/SoftLayer_Ticket.py @@ -36,7 +36,7 @@ edit = True addUpdate = {} -list = getObject +gatList = getObject addAttachedHardware = { "id": 123, From 3e0398d9c6aa5884d9b47ce928afbbc33d229034 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Sep 2019 17:28:58 -0400 Subject: [PATCH 0158/1582] using the item os keyName instead of referenceCode. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fb85c3282..390b8ade1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -403,7 +403,7 @@ def get_create_options(self): if item['itemCategory']['categoryCode'] == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], - 'key': item['softwareDescription']['referenceCode'], + 'key': item['keyName'] }) # Port speeds From 03dc5b36c1284aacb4b1e864ced27c072ee404d7 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Sep 2019 18:04:21 -0400 Subject: [PATCH 0159/1582] updated code that looks up price ids. --- SoftLayer/managers/hardware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 390b8ade1..6cbeb91fd 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -777,8 +777,7 @@ def _get_os_price_id(items, os, location): 'itemCategory', 'categoryCode') != 'os', utils.lookup(item, - 'softwareDescription', - 'referenceCode') != os]): + 'keyName') != os]): continue for price in item['prices']: From d4d2feb75b3b1be08333b384ac884e89376bc42c Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 24 Sep 2019 18:52:47 -0400 Subject: [PATCH 0160/1582] updated hardware and server tests --- tests/CLI/modules/server_tests.py | 4 ++-- tests/managers/hardware_tests.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f14118bc1..70d4c3167 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -317,7 +317,7 @@ def test_create_options(self): {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'UBUNTU_14_64'}], + 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT'}], [{'port_speed': '10 Mbps Public & Private Network Uplinks', 'value': '10'}], [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] @@ -336,7 +336,7 @@ def test_create_server(self, order_mock): '--domain=example.com', '--datacenter=TEST00', '--port-speed=100', - '--os=UBUNTU_12_64', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', '--no-public', '--key=10', ]) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 7cff11303..cf10224a4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -123,7 +123,7 @@ def test_get_create_options(self): expected = { 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], - 'operating_systems': [{'key': 'UBUNTU_14_64', + 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64'}], 'port_speeds': [{ 'key': '10', @@ -180,7 +180,7 @@ def test_generate_create_dict_invalid_size(self): 'hostname': 'unicorn', 'domain': 'giggles.woo', 'location': 'wdc01', - 'os': 'UBUNTU_14_64', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, } @@ -194,7 +194,7 @@ def test_generate_create_dict(self): 'hostname': 'unicorn', 'domain': 'giggles.woo', 'location': 'wdc01', - 'os': 'UBUNTU_14_64', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, 'hourly': True, 'extras': ['1_IPV6_ADDRESS'], @@ -514,11 +514,11 @@ def test_get_bandwidth_price_mismatched(self): def test_get_os_price_mismatched(self): items = [ {'itemCategory': {'categoryCode': 'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'keyName': 'OS_TEST', 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] }, {'itemCategory': {'categoryCode': 'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'keyName': 'OS_TEST', 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] }, ] @@ -532,7 +532,7 @@ def test_get_os_price_mismatched(self): } } } - result = managers.hardware._get_os_price_id(items, 'TEST_OS', location) + result = managers.hardware._get_os_price_id(items, 'OS_TEST', location) self.assertEqual(3, result) def test_get_default_price_id_item_not_first(self): From 89f9d9e9727951d11988905b9e1e313c10dcbb29 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Sep 2019 17:28:58 -0400 Subject: [PATCH 0161/1582] using the item os keyName instead of referenceCode. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fb85c3282..390b8ade1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -403,7 +403,7 @@ def get_create_options(self): if item['itemCategory']['categoryCode'] == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], - 'key': item['softwareDescription']['referenceCode'], + 'key': item['keyName'] }) # Port speeds From 1b820b0a1ad8f6bd2453610378bd1bbb5aed6c3b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Sep 2019 18:04:21 -0400 Subject: [PATCH 0162/1582] updated code that looks up price ids. --- SoftLayer/managers/hardware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 390b8ade1..6cbeb91fd 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -777,8 +777,7 @@ def _get_os_price_id(items, os, location): 'itemCategory', 'categoryCode') != 'os', utils.lookup(item, - 'softwareDescription', - 'referenceCode') != os]): + 'keyName') != os]): continue for price in item['prices']: From 755a5b6b08b021947d7fc886becf01ecea497c7a Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 24 Sep 2019 18:52:47 -0400 Subject: [PATCH 0163/1582] updated hardware and server tests --- tests/CLI/modules/server_tests.py | 4 ++-- tests/managers/hardware_tests.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f14118bc1..70d4c3167 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -317,7 +317,7 @@ def test_create_options(self): {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'UBUNTU_14_64'}], + 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT'}], [{'port_speed': '10 Mbps Public & Private Network Uplinks', 'value': '10'}], [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] @@ -336,7 +336,7 @@ def test_create_server(self, order_mock): '--domain=example.com', '--datacenter=TEST00', '--port-speed=100', - '--os=UBUNTU_12_64', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', '--no-public', '--key=10', ]) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 7cff11303..cf10224a4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -123,7 +123,7 @@ def test_get_create_options(self): expected = { 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], - 'operating_systems': [{'key': 'UBUNTU_14_64', + 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64'}], 'port_speeds': [{ 'key': '10', @@ -180,7 +180,7 @@ def test_generate_create_dict_invalid_size(self): 'hostname': 'unicorn', 'domain': 'giggles.woo', 'location': 'wdc01', - 'os': 'UBUNTU_14_64', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, } @@ -194,7 +194,7 @@ def test_generate_create_dict(self): 'hostname': 'unicorn', 'domain': 'giggles.woo', 'location': 'wdc01', - 'os': 'UBUNTU_14_64', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, 'hourly': True, 'extras': ['1_IPV6_ADDRESS'], @@ -514,11 +514,11 @@ def test_get_bandwidth_price_mismatched(self): def test_get_os_price_mismatched(self): items = [ {'itemCategory': {'categoryCode': 'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'keyName': 'OS_TEST', 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] }, {'itemCategory': {'categoryCode': 'os'}, - 'softwareDescription': {'referenceCode': 'TEST_OS'}, + 'keyName': 'OS_TEST', 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] }, ] @@ -532,7 +532,7 @@ def test_get_os_price_mismatched(self): } } } - result = managers.hardware._get_os_price_id(items, 'TEST_OS', location) + result = managers.hardware._get_os_price_id(items, 'OS_TEST', location) self.assertEqual(3, result) def test_get_default_price_id_item_not_first(self): From f782312050a799667a4ebf1c16f90865813f6818 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 24 Sep 2019 18:20:42 -0500 Subject: [PATCH 0164/1582] Fix issues introduced with pylint 2.4.0 --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/deprecated.py | 2 +- SoftLayer/CLI/firewall/edit.py | 12 ++++-------- SoftLayer/CLI/loadbal/pools.py | 2 +- SoftLayer/CLI/user/create.py | 2 +- SoftLayer/CLI/virt/create.py | 4 ++-- SoftLayer/managers/firewall.py | 3 ++- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/user.py | 3 +-- SoftLayer/managers/vs.py | 2 +- 10 files changed, 15 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 6b4ab007b..f34e0c4f9 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -9,6 +9,7 @@ import os import sys import time +import traceback import types import click @@ -196,7 +197,6 @@ def main(reraise_exceptions=False, **kwargs): if reraise_exceptions: raise - import traceback print("An unexpected error has occured:") print(str(traceback.format_exc())) print("Feel free to report this error as it is likely a bug:") diff --git a/SoftLayer/CLI/deprecated.py b/SoftLayer/CLI/deprecated.py index 3b1da4b6c..0609a9246 100644 --- a/SoftLayer/CLI/deprecated.py +++ b/SoftLayer/CLI/deprecated.py @@ -11,4 +11,4 @@ def main(): """Main function for the deprecated 'sl' command.""" print("ERROR: Use the 'slcli' command instead.", file=sys.stderr) print("> slcli %s" % ' '.join(sys.argv[1:]), file=sys.stderr) - exit(-1) + sys.exit(-1) diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py index f58be4439..d2bb5bb2a 100644 --- a/SoftLayer/CLI/firewall/edit.py +++ b/SoftLayer/CLI/firewall/edit.py @@ -154,11 +154,9 @@ def cli(env, identifier): try: rules = parse_rules(edited_rules) if firewall_type == 'vlan': - rules = mgr.edit_dedicated_fwl_rules(firewall_id, - rules) + mgr.edit_dedicated_fwl_rules(firewall_id, rules) else: - rules = mgr.edit_standard_fwl_rules(firewall_id, - rules) + mgr.edit_standard_fwl_rules(firewall_id, rules) break except (SoftLayer.SoftLayerError, ValueError) as error: env.out("Unexpected error({%s})" % (error)) @@ -169,10 +167,8 @@ def cli(env, identifier): if formatting.confirm("Would you like to submit the " "rules. Continue?"): continue - else: - raise exceptions.CLIAbort('Aborted.') - else: raise exceptions.CLIAbort('Aborted.') - env.fout('Firewall updated!') + raise exceptions.CLIAbort('Aborted.') + env.fout('Firewall updated!') else: raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index b8da27ac9..148395cd2 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -177,7 +177,7 @@ def l7pool_add(env, identifier, **args): 'protocol': args.get('protocol') } - pool_members = [member for member in args.get('server')] + pool_members = list(args.get('server')) pool_health = { 'interval': args.get('healthinterval'), diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index ed6d74747..6ab19884a 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -91,7 +91,7 @@ def cli(env, username, email, password, from_user, template, api_key): def generate_password(): """Returns a 23 character random string, with 3 special characters at the end""" if sys.version_info > (3, 6): - import secrets # pylint: disable=import-error + import secrets # pylint: disable=import-error,import-outside-toplevel alphabet = string.ascii_letters + string.digits password = ''.join(secrets.choice(alphabet) for i in range(20)) special = ''.join(secrets.choice(string.punctuation) for i in range(3)) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 70430bc8f..d9864a890 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -130,11 +130,11 @@ def _parse_create_args(client, args): if args.get('public_security_group'): pub_groups = args.get('public_security_group') - data['public_security_groups'] = [group for group in pub_groups] + data['public_security_groups'] = list(pub_groups) if args.get('private_security_group'): priv_groups = args.get('private_security_group') - data['private_security_groups'] = [group for group in priv_groups] + data['private_security_groups'] = list(priv_groups) if args.get('tag', False): data['tags'] = ','.join(args['tag']) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 8afe57f1b..2b1d8e452 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -282,7 +282,8 @@ def edit_standard_fwl_rules(self, firewall_id, rules): """Edit the rules for standard firewall. :param integer firewall_id: the instance ID of the standard firewall - :param dict rules: the rules to be pushed on the firewall + :param list rules: the rules to be pushed on the firewall as defined by + SoftLayer_Network_Firewall_Update_Request_Rule """ rule_svc = self.client['Network_Firewall_Update_Request'] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fb85c3282..73297dea6 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -281,7 +281,7 @@ def reload(self, hardware_id, post_uri=None, ssh_keys=None): config['customProvisionScriptUri'] = post_uri if ssh_keys: - config['sshKeyIds'] = [key_id for key_id in ssh_keys] + config['sshKeyIds'] = list(ssh_keys) return self.hardware.reloadOperatingSystem('FORCE', config, id=hardware_id) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 82cf62cd2..247071381 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -132,8 +132,7 @@ def permissions_from_user(self, user_id, from_user_id): # If permission does not exist for from_user_id add it to the list to be removed if _keyname_search(from_permissions, permission['keyName']): continue - else: - remove_permissions.append({'keyName': permission['keyName']}) + remove_permissions.append({'keyName': permission['keyName']}) self.remove_permissions(user_id, remove_permissions) return True diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index de845096b..9f2b6c94c 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -303,7 +303,7 @@ def reload_instance(self, instance_id, config['customProvisionScriptUri'] = post_uri if ssh_keys: - config['sshKeyIds'] = [key_id for key_id in ssh_keys] + config['sshKeyIds'] = list(ssh_keys) if image_id: config['imageTemplateId'] = image_id From ef284ae196d7d18aeadec6995c258d13d4c0b946 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 25 Sep 2019 13:51:19 -0500 Subject: [PATCH 0165/1582] skeleton for autoscale --- SoftLayer/CLI/autoscale/__init__.py | 0 SoftLayer/CLI/autoscale/list.py | 25 +++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 3 +++ SoftLayer/managers/autoscale.py | 19 +++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 SoftLayer/CLI/autoscale/__init__.py create mode 100644 SoftLayer/CLI/autoscale/list.py create mode 100644 SoftLayer/managers/autoscale.py diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/autoscale/list.py b/SoftLayer/CLI/autoscale/list.py new file mode 100644 index 000000000..031df2cf3 --- /dev/null +++ b/SoftLayer/CLI/autoscale/list.py @@ -0,0 +1,25 @@ +"""List virtual servers.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.managers.autoscale import AutoScaleManager + + +@click.command() +@environment.pass_env +def cli(env): + """List AutoScale Groups.""" + + autoscale = AutoScaleManager(env.client) + groups = autoscale.list() + print(groups) + # table = formatting.Table() + + + # env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fb33ee9e2..a97fb9d4b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -302,6 +302,9 @@ ('report', 'SoftLayer.CLI.report'), ('report:bandwidth', 'SoftLayer.CLI.report.bandwidth:cli'), + + ('autoscale', 'SoftLayer.CLI.autoscale'), + ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py new file mode 100644 index 000000000..7e823f98a --- /dev/null +++ b/SoftLayer/managers/autoscale.py @@ -0,0 +1,19 @@ +""" + SoftLayer.autoscale + ~~~~~~~~~~~~ + Autoscale manager + + :license: MIT, see LICENSE for more details. +""" + + + +class AutoScaleManager(object): + + def __init__(self, client): + self.client = client + + + def list(self): + print("LISTING....") + return True \ No newline at end of file From e00e4cff8de50e11bfa9053d50d414e75766c330 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 25 Sep 2019 16:44:43 -0500 Subject: [PATCH 0166/1582] autoscale detail and list --- SoftLayer/CLI/autoscale/detail.py | 47 +++++++++++++++++++++++++++++++ SoftLayer/CLI/autoscale/list.py | 18 +++++++++--- SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/autoscale.py | 15 ++++++++-- 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 SoftLayer/CLI/autoscale/detail.py diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py new file mode 100644 index 000000000..5fa987154 --- /dev/null +++ b/SoftLayer/CLI/autoscale/detail.py @@ -0,0 +1,47 @@ +"""List Autoscale groups.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer import utils + +from pprint import pprint as pp + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """List AutoScale Groups.""" + + autoscale = AutoScaleManager(env.client) + group = autoscale.details(identifier) + # print(groups) + # pp(group) + table = formatting.KeyValueTable(["Name", "Value"]) + + table.add_row(['Id', group.get('id')]) + # Ideally we would use regionalGroup->preferredDatacenter, but that generates an error. + table.add_row(['Datacenter', group['regionalGroup']['locations'][0]['longName']]) + table.add_row(['Termination', utils.lookup(group, 'terminationPolicy', 'name')]) + table.add_row(['Minimum Members', group.get('minimumMemberCount')]) + table.add_row(['Maximum Members', group.get('maximumMemberCount')]) + table.add_row(['Current Members', group.get('virtualGuestMemberCount')]) + table.add_row(['Last Action', utils.clean_time(group.get('lastActionDate'))]) + + for network in group.get('networkVlans'): + network_type = utils.lookup(network, 'networkVlan', 'networkSpace') + router = utils.lookup(network, 'networkVlan', 'primaryRouter', 'hostname') + vlan_number = utils.lookup(network, 'networkVlan', 'vlanNumber') + vlan_name = "{}.{}".format(router, vlan_number) + table.add_row([network_type, vlan_name]) + + + + + env.fout(table) diff --git a/SoftLayer/CLI/autoscale/list.py b/SoftLayer/CLI/autoscale/list.py index 031df2cf3..2e427285b 100644 --- a/SoftLayer/CLI/autoscale/list.py +++ b/SoftLayer/CLI/autoscale/list.py @@ -1,4 +1,4 @@ -"""List virtual servers.""" +"""List Autoscale groups.""" # :license: MIT, see LICENSE for more details. import click @@ -9,7 +9,9 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer import utils +from pprint import pprint as pp @click.command() @environment.pass_env @@ -18,8 +20,16 @@ def cli(env): autoscale = AutoScaleManager(env.client) groups = autoscale.list() - print(groups) - # table = formatting.Table() + # print(groups) + # pp(groups) + table = formatting.Table(["Id", "Name", "Status", "Min/Max", "Running"]) + for group in groups: + status = utils.lookup(group, 'status', 'name') + min_max = "{}/{}".format(group.get('minimumMemberCount', '-'), group.get('maximumMemberCount'), '-') + table.add_row([ + group.get('id'), group.get('name'), status, min_max, group.get('virtualGuestMemberCount') + ]) - # env.fout(table) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a97fb9d4b..9b144e396 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -305,6 +305,7 @@ ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), + ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 7e823f98a..37f244aca 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -14,6 +14,15 @@ def __init__(self, client): self.client = client - def list(self): - print("LISTING....") - return True \ No newline at end of file + def list(self, mask=None): + if not mask: + mask = "mask[status,virtualGuestMemberCount]" + + return self.client.call('SoftLayer_Account', 'getScaleGroups', mask=mask, iter=True) + + def details(self, identifier, mask=None): + if not mask: + mask = """mask[virtualGuestMembers, terminationPolicy, policies, virtualGuestMemberCount, + networkVlans[networkVlanId,networkVlan[networkSpace,primaryRouter[hostname]]], + loadBalancers, regionalGroup[locations]]""" + return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask) \ No newline at end of file From feb8f9aa66687c0df799dfe764f6e9d01bf4c86a Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 26 Sep 2019 17:55:25 -0500 Subject: [PATCH 0167/1582] autoscale details mostly done --- SoftLayer/CLI/autoscale/detail.py | 62 ++++++++++++++++++++++++++++++- SoftLayer/managers/autoscale.py | 14 ++++++- 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py index 5fa987154..bf11de900 100644 --- a/SoftLayer/CLI/autoscale/detail.py +++ b/SoftLayer/CLI/autoscale/detail.py @@ -23,7 +23,9 @@ def cli(env, identifier): group = autoscale.details(identifier) # print(groups) # pp(group) - table = formatting.KeyValueTable(["Name", "Value"]) + + # Group Config Table + table = formatting.KeyValueTable(["Group", "Value"]) table.add_row(['Id', group.get('id')]) # Ideally we would use regionalGroup->preferredDatacenter, but that generates an error. @@ -32,6 +34,7 @@ def cli(env, identifier): table.add_row(['Minimum Members', group.get('minimumMemberCount')]) table.add_row(['Maximum Members', group.get('maximumMemberCount')]) table.add_row(['Current Members', group.get('virtualGuestMemberCount')]) + table.add_row(['Cooldown', "{} seconds".format(group.get('cooldown'))]) table.add_row(['Last Action', utils.clean_time(group.get('lastActionDate'))]) for network in group.get('networkVlans'): @@ -41,7 +44,62 @@ def cli(env, identifier): vlan_name = "{}.{}".format(router, vlan_number) table.add_row([network_type, vlan_name]) + env.fout(table) + # Template Config Table + config_table = formatting.KeyValueTable(["Template", "Value"]) + template = group.get('virtualGuestMemberTemplate') + + config_table.add_row(['Hostname', template.get('hostname')]) + config_table.add_row(['Domain', template.get('domain')]) + config_table.add_row(['Core', template.get('startCpus')]) + config_table.add_row(['Ram', template.get('maxMemory')]) + network = template.get('networkComponents') + config_table.add_row(['Network', network[0]['maxSpeed']]) + ssh_keys = template.get('sshKeys', []) + ssh_manager = SoftLayer.SshKeyManager(env.client) + for key in ssh_keys: + # Label isn't included when retrieved from the AutoScale group... + ssh_key = ssh_manager.get_key(key.get('id')) + config_table.add_row(['SSH Key {}'.format(ssh_key.get('id')), ssh_key.get('label')]) + disks = template.get('blockDevices') + disk_type = "SAN" + if template.get('localDiskFlag'): + disk_type = "Local" + for disk in disks: + disk_image = disk.get('diskImage') + config_table.add_row(['{} Disk {}'.format(disk_type, disk.get('device')), disk_image.get('capacity')]) + config_table.add_row(['OS', template.get('operatingSystemReferenceCode')]) + config_table.add_row(['Post Install', template.get('postInstallScriptUri') or 'None']) - env.fout(table) + env.fout(config_table) + + + # Policy Config Table + policy_table = formatting.KeyValueTable(["Policy", "Cooldown"]) + policies = group.get('policies') + # pp(policies) + for policy in policies: + policy_table.add_row([policy.get('name'), policy.get('cooldown') or group.get('cooldown')]) + # full_policy = autoscale.get_policy(policy.get('id')) + # pp(full_policy) + + env.fout(policy_table) + + # LB Config Table + # Not sure if this still still a thing? + # lb_table = formatting.KeyValueTable(["Load Balancer", "Value"]) + # loadbal = group.get('loadBalancers') + + # env.fout(lb_table) + + # Active Guests + member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Active Guests") + guests = group.get('virtualGuestMembers') + for guest in guests: + real_guest = guest.get('virtualGuest') + member_table.add_row([ + guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('provisionDate')) + ]) + env.fout(member_table) diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 37f244aca..302e223a1 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -22,7 +22,17 @@ def list(self, mask=None): def details(self, identifier, mask=None): if not mask: - mask = """mask[virtualGuestMembers, terminationPolicy, policies, virtualGuestMemberCount, + mask = """mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], terminationPolicy, + virtualGuestMemberCount, virtualGuestMemberTemplate[sshKeys], + policies[id,name,createDate,cooldown,actions,triggers,scaleActions], networkVlans[networkVlanId,networkVlan[networkSpace,primaryRouter[hostname]]], loadBalancers, regionalGroup[locations]]""" - return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask) \ No newline at end of file + return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask) + + def get_policy(self, identifier, mask=None): + if not mask: + mask = """mask[cooldown, createDate, id, name, actions, triggers[type] + + ]""" + + return self.client.call('SoftLayer_Scale_Policy', 'getObject', id=identifier, mask=mask) From ebc80cd56bb406f014e48cbb8bd7b1fb33b419ea Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 27 Sep 2019 13:40:24 -0500 Subject: [PATCH 0168/1582] added autoscale scale commands --- SoftLayer/CLI/autoscale/detail.py | 21 ++---------- SoftLayer/CLI/autoscale/list.py | 3 -- SoftLayer/CLI/autoscale/scale.py | 56 +++++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/autoscale.py | 36 ++++++++++++++++++++ 5 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 SoftLayer/CLI/autoscale/scale.py diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py index bf11de900..31c29bed7 100644 --- a/SoftLayer/CLI/autoscale/detail.py +++ b/SoftLayer/CLI/autoscale/detail.py @@ -1,28 +1,23 @@ -"""List Autoscale groups.""" +"""Get details of an Autoscale groups.""" # :license: MIT, see LICENSE for more details. import click import SoftLayer -from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers from SoftLayer.managers.autoscale import AutoScaleManager from SoftLayer import utils -from pprint import pprint as pp @click.command() @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """List AutoScale Groups.""" + """Get details of an Autoscale groups.""" autoscale = AutoScaleManager(env.client) group = autoscale.details(identifier) - # print(groups) - # pp(group) # Group Config Table table = formatting.KeyValueTable(["Group", "Value"]) @@ -46,7 +41,6 @@ def cli(env, identifier): env.fout(table) - # Template Config Table config_table = formatting.KeyValueTable(["Template", "Value"]) template = group.get('virtualGuestMemberTemplate') @@ -75,25 +69,14 @@ def cli(env, identifier): env.fout(config_table) - # Policy Config Table policy_table = formatting.KeyValueTable(["Policy", "Cooldown"]) policies = group.get('policies') - # pp(policies) for policy in policies: policy_table.add_row([policy.get('name'), policy.get('cooldown') or group.get('cooldown')]) - # full_policy = autoscale.get_policy(policy.get('id')) - # pp(full_policy) env.fout(policy_table) - # LB Config Table - # Not sure if this still still a thing? - # lb_table = formatting.KeyValueTable(["Load Balancer", "Value"]) - # loadbal = group.get('loadBalancers') - - # env.fout(lb_table) - # Active Guests member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Active Guests") guests = group.get('virtualGuestMembers') diff --git a/SoftLayer/CLI/autoscale/list.py b/SoftLayer/CLI/autoscale/list.py index 2e427285b..043a0892e 100644 --- a/SoftLayer/CLI/autoscale/list.py +++ b/SoftLayer/CLI/autoscale/list.py @@ -4,14 +4,11 @@ import click import SoftLayer -from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.CLI import helpers from SoftLayer.managers.autoscale import AutoScaleManager from SoftLayer import utils -from pprint import pprint as pp @click.command() @environment.pass_env diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py new file mode 100644 index 000000000..f93a5953a --- /dev/null +++ b/SoftLayer/CLI/autoscale/scale.py @@ -0,0 +1,56 @@ +"""Scales an Autoscale group""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--up/--down', 'scale_up', is_flag=True, default=True, + help="'--up' adds guests, '--down' removes guests.") +@click.option('--by/--to', 'scale_by', is_flag=True, required=True, + help="'--by' will add/remove the specified number of guests." \ + " '--to' will add/remove a number of guests to get the group's guest count to the specified number.") +@click.option('--amount', required=True, type=click.INT, help="Number of guests for the scale action.") +@environment.pass_env +def cli(env, identifier, scale_up, scale_by, amount): + """Scales an Autoscale group. Bypasses a scale group's cooldown period.""" + + autoscale = AutoScaleManager(env.client) + + # Scale By, and go down, need to use negative amount + if not scale_up and scale_by: + amount = amount * -1 + + result = [] + if scale_by: + click.secho("Scaling group {} by {}".format(identifier, amount), fg='green') + result = autoscale.scale(identifier, amount) + else: + click.secho("Scaling group {} to {}".format(identifier, amount), fg='green') + result = autoscale.scale_to(identifier, amount) + + try: + # Check if the first guest has a cancellation date, assume we are removing guests if it is. + cancellationDate = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False + except (IndexError, KeyError, TypeError) as e: + cancellationDate = False + + if cancellationDate: + member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") + else: + member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests") + + for guest in result: + real_guest = guest.get('virtualGuest') + member_table.add_row([ + guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('createDate')) + ]) + + env.fout(member_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 9b144e396..1d7575f5d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -306,6 +306,7 @@ ('autoscale', 'SoftLayer.CLI.autoscale'), ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), + ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 302e223a1..fe4d1475c 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -15,12 +15,23 @@ def __init__(self, client): def list(self, mask=None): + """Calls SoftLayer_Account getScaleGroups()_ + + :param mask: optional SoftLayer_Scale_Group objectMask + .. getScaleGroups(): https://sldn.softlayer.com/reference/services/SoftLayer_Account/getScaleGroups/ + """ if not mask: mask = "mask[status,virtualGuestMemberCount]" return self.client.call('SoftLayer_Account', 'getScaleGroups', mask=mask, iter=True) def details(self, identifier, mask=None): + """Calls SoftLayer_Scale_Group getObject()_ + + :param identifier: SoftLayer_Scale_Group id + :param mask: optional SoftLayer_Scale_Group objectMask + .. _getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/ + """ if not mask: mask = """mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], terminationPolicy, virtualGuestMemberCount, virtualGuestMemberTemplate[sshKeys], @@ -30,9 +41,34 @@ def details(self, identifier, mask=None): return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask) def get_policy(self, identifier, mask=None): + """Calls SoftLayer_Scale_Policy getObject()_ + + :param identifier: SoftLayer_Scale_Policy id + :param mask: optional SoftLayer_Scale_Policy objectMask + .. _getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Policy/getObject/ + """ if not mask: mask = """mask[cooldown, createDate, id, name, actions, triggers[type] ]""" return self.client.call('SoftLayer_Scale_Policy', 'getObject', id=identifier, mask=mask) + + def scale(self, identifier, amount): + """Calls SoftLayer_Scale_Group scale()_ + + :param identifier: SoftLayer_Scale_Group Id + :param amount: positive or negative number to scale the group by + + .. _scale(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scale/ + """ + return self.client.call('SoftLayer_Scale_Group', 'scale', amount, id=identifier) + + def scale_to(self, identifier, amount): + """Calls SoftLayer_Scale_Group scaleTo()_ + + :param identifier: SoftLayer_Scale_Group Id + :param amount: number to scale the group to. + .. _scaleTo(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/ + """ + return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier) \ No newline at end of file From 645df87fb2b75b572ab03f420226cefb7762cf28 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 27 Sep 2019 16:20:33 -0500 Subject: [PATCH 0169/1582] autoscale logs, tox fixes --- SoftLayer/CLI/autoscale/detail.py | 9 ++++++-- SoftLayer/CLI/autoscale/list.py | 9 +++----- SoftLayer/CLI/autoscale/logs.py | 37 +++++++++++++++++++++++++++++++ SoftLayer/CLI/autoscale/scale.py | 15 ++++++------- SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/autoscale.py | 16 ++++++++++--- 6 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 SoftLayer/CLI/autoscale/logs.py diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py index 31c29bed7..88fec1c18 100644 --- a/SoftLayer/CLI/autoscale/detail.py +++ b/SoftLayer/CLI/autoscale/detail.py @@ -21,6 +21,8 @@ def cli(env, identifier): # Group Config Table table = formatting.KeyValueTable(["Group", "Value"]) + table.align['Group'] = 'l' + table.align['Value'] = 'l' table.add_row(['Id', group.get('id')]) # Ideally we would use regionalGroup->preferredDatacenter, but that generates an error. @@ -31,7 +33,7 @@ def cli(env, identifier): table.add_row(['Current Members', group.get('virtualGuestMemberCount')]) table.add_row(['Cooldown', "{} seconds".format(group.get('cooldown'))]) table.add_row(['Last Action', utils.clean_time(group.get('lastActionDate'))]) - + for network in group.get('networkVlans'): network_type = utils.lookup(network, 'networkVlan', 'networkSpace') router = utils.lookup(network, 'networkVlan', 'primaryRouter', 'hostname') @@ -43,8 +45,11 @@ def cli(env, identifier): # Template Config Table config_table = formatting.KeyValueTable(["Template", "Value"]) + config_table.align['Template'] = 'l' + config_table.align['Value'] = 'l' + template = group.get('virtualGuestMemberTemplate') - + config_table.add_row(['Hostname', template.get('hostname')]) config_table.add_row(['Domain', template.get('domain')]) config_table.add_row(['Core', template.get('startCpus')]) diff --git a/SoftLayer/CLI/autoscale/list.py b/SoftLayer/CLI/autoscale/list.py index 043a0892e..2d360714c 100644 --- a/SoftLayer/CLI/autoscale/list.py +++ b/SoftLayer/CLI/autoscale/list.py @@ -3,7 +3,6 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.autoscale import AutoScaleManager @@ -17,16 +16,14 @@ def cli(env): autoscale = AutoScaleManager(env.client) groups = autoscale.list() - # print(groups) - # pp(groups) - table = formatting.Table(["Id", "Name", "Status", "Min/Max", "Running"]) + table = formatting.Table(["Id", "Name", "Status", "Min/Max", "Running"]) + table.align['Name'] = 'l' for group in groups: status = utils.lookup(group, 'status', 'name') - min_max = "{}/{}".format(group.get('minimumMemberCount', '-'), group.get('maximumMemberCount'), '-') + min_max = "{}/{}".format(group.get('minimumMemberCount'), group.get('maximumMemberCount')) table.add_row([ group.get('id'), group.get('name'), status, min_max, group.get('virtualGuestMemberCount') ]) - env.fout(table) diff --git a/SoftLayer/CLI/autoscale/logs.py b/SoftLayer/CLI/autoscale/logs.py new file mode 100644 index 000000000..6c4c401b3 --- /dev/null +++ b/SoftLayer/CLI/autoscale/logs.py @@ -0,0 +1,37 @@ +"""Retreive logs for an autoscale group""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--date-min', '-d', 'date_min', type=click.DateTime(formats=["%Y-%m-%d", "%m/%d/%Y"]), + help='Earliest date to retreive logs for.') +@environment.pass_env +def cli(env, identifier, date_min): + """Retreive logs for an autoscale group""" + + autoscale = AutoScaleManager(env.client) + + mask = "mask[id,createDate,description]" + object_filter = {} + if date_min: + object_filter['logs'] = { + 'createDate': { + 'operation': 'greaterThanDate', + 'options': [{'name': 'date', 'value': [date_min.strftime("%m/%d/%Y")]}] + } + } + + logs = autoscale.get_logs(identifier, mask=mask, object_filter=object_filter) + table = formatting.Table(['Date', 'Entry'], title="Logs") + table.align['Entry'] = 'l' + for log in logs: + table.add_row([utils.clean_time(log.get('createDate')), log.get('description')]) + env.fout(table) diff --git a/SoftLayer/CLI/autoscale/scale.py b/SoftLayer/CLI/autoscale/scale.py index f93a5953a..69fe1305a 100644 --- a/SoftLayer/CLI/autoscale/scale.py +++ b/SoftLayer/CLI/autoscale/scale.py @@ -3,7 +3,6 @@ import click -import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.autoscale import AutoScaleManager @@ -15,7 +14,7 @@ @click.option('--up/--down', 'scale_up', is_flag=True, default=True, help="'--up' adds guests, '--down' removes guests.") @click.option('--by/--to', 'scale_by', is_flag=True, required=True, - help="'--by' will add/remove the specified number of guests." \ + help="'--by' will add/remove the specified number of guests." " '--to' will add/remove a number of guests to get the group's guest count to the specified number.") @click.option('--amount', required=True, type=click.INT, help="Number of guests for the scale action.") @environment.pass_env @@ -30,19 +29,19 @@ def cli(env, identifier, scale_up, scale_by, amount): result = [] if scale_by: - click.secho("Scaling group {} by {}".format(identifier, amount), fg='green') + click.secho("Scaling group {} by {}".format(identifier, amount), fg='green') result = autoscale.scale(identifier, amount) else: - click.secho("Scaling group {} to {}".format(identifier, amount), fg='green') + click.secho("Scaling group {} to {}".format(identifier, amount), fg='green') result = autoscale.scale_to(identifier, amount) try: # Check if the first guest has a cancellation date, assume we are removing guests if it is. - cancellationDate = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False - except (IndexError, KeyError, TypeError) as e: - cancellationDate = False + cancel_date = result[0]['virtualGuest']['billingItem']['cancellationDate'] or False + except (IndexError, KeyError, TypeError): + cancel_date = False - if cancellationDate: + if cancel_date: member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Cancelled Guests") else: member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Added Guests") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 1d7575f5d..0c47ce90d 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -307,6 +307,7 @@ ('autoscale:list', 'SoftLayer.CLI.autoscale.list:cli'), ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), ('autoscale:scale', 'SoftLayer.CLI.autoscale.scale:cli'), + ('autoscale:logs', 'SoftLayer.CLI.autoscale.logs:cli'), ] ALL_ALIASES = { diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index fe4d1475c..88e8270ca 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -7,13 +7,12 @@ """ - class AutoScaleManager(object): + """Manager for interacting with Autoscale instances.""" def __init__(self, client): self.client = client - def list(self, mask=None): """Calls SoftLayer_Account getScaleGroups()_ @@ -71,4 +70,15 @@ def scale_to(self, identifier, amount): :param amount: number to scale the group to. .. _scaleTo(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/ """ - return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier) \ No newline at end of file + return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier) + + def get_logs(self, identifier, mask=None, object_filter=None): + """Calls SoftLayer_Scale_Group getLogs()_ + + :param identifier: SoftLayer_Scale_Group Id + :param mask: optional SoftLayer_Scale_Group_Log objectMask + :param object_filter: optional SoftLayer_Scale_Group_Log objectFilter + .. getLogs(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getLogs/ + """ + return self.client.call('SoftLayer_Scale_Group', 'getLogs', id=identifier, mask=mask, filter=object_filter, + iter=True) From 53c17e1766d07d78bca11115aaad1bdb3e77618f Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 27 Sep 2019 16:59:39 -0500 Subject: [PATCH 0170/1582] #627 autoscale feature documentation --- SoftLayer/managers/autoscale.py | 37 +++++++++++++++++++++------------ docs/api/managers/autoscale.rst | 5 +++++ docs/cli/autoscale.rst | 31 +++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 docs/api/managers/autoscale.rst create mode 100644 docs/cli/autoscale.rst diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 88e8270ca..7f5d3d321 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -1,6 +1,6 @@ """ SoftLayer.autoscale - ~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~ Autoscale manager :license: MIT, see LICENSE for more details. @@ -14,10 +14,12 @@ def __init__(self, client): self.client = client def list(self, mask=None): - """Calls SoftLayer_Account getScaleGroups()_ + """Calls `SoftLayer_Account::getScaleGroups()`_ :param mask: optional SoftLayer_Scale_Group objectMask - .. getScaleGroups(): https://sldn.softlayer.com/reference/services/SoftLayer_Account/getScaleGroups/ + + .. _SoftLayer_Account::getScaleGroups(): + https://sldn.softlayer.com/reference/services/SoftLayer_Account/getScaleGroups/ """ if not mask: mask = "mask[status,virtualGuestMemberCount]" @@ -25,11 +27,13 @@ def list(self, mask=None): return self.client.call('SoftLayer_Account', 'getScaleGroups', mask=mask, iter=True) def details(self, identifier, mask=None): - """Calls SoftLayer_Scale_Group getObject()_ + """Calls `SoftLayer_Scale_Group::getObject()`_ :param identifier: SoftLayer_Scale_Group id :param mask: optional SoftLayer_Scale_Group objectMask - .. _getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/ + + .. _SoftLayer_Scale_Group::getObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/ """ if not mask: mask = """mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], terminationPolicy, @@ -40,11 +44,13 @@ def details(self, identifier, mask=None): return self.client.call('SoftLayer_Scale_Group', 'getObject', id=identifier, mask=mask) def get_policy(self, identifier, mask=None): - """Calls SoftLayer_Scale_Policy getObject()_ + """Calls `SoftLayer_Scale_Policy::getObject()`_ :param identifier: SoftLayer_Scale_Policy id :param mask: optional SoftLayer_Scale_Policy objectMask - .. _getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Policy/getObject/ + + .. _SoftLayer_Scale_Policy::getObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Policy/getObject/ """ if not mask: mask = """mask[cooldown, createDate, id, name, actions, triggers[type] @@ -54,31 +60,36 @@ def get_policy(self, identifier, mask=None): return self.client.call('SoftLayer_Scale_Policy', 'getObject', id=identifier, mask=mask) def scale(self, identifier, amount): - """Calls SoftLayer_Scale_Group scale()_ + """Calls `SoftLayer_Scale_Group::scale()`_ :param identifier: SoftLayer_Scale_Group Id :param amount: positive or negative number to scale the group by - .. _scale(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scale/ + .. _SoftLayer_Scale_Group::scale(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scale/ """ return self.client.call('SoftLayer_Scale_Group', 'scale', amount, id=identifier) def scale_to(self, identifier, amount): - """Calls SoftLayer_Scale_Group scaleTo()_ + """Calls `SoftLayer_Scale_Group::scaleTo()`_ :param identifier: SoftLayer_Scale_Group Id :param amount: number to scale the group to. - .. _scaleTo(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/ + + .. _SoftLayer_Scale_Group::scaleTo(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/ """ return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier) def get_logs(self, identifier, mask=None, object_filter=None): - """Calls SoftLayer_Scale_Group getLogs()_ + """Calls `SoftLayer_Scale_Group::getLogs()`_ :param identifier: SoftLayer_Scale_Group Id :param mask: optional SoftLayer_Scale_Group_Log objectMask :param object_filter: optional SoftLayer_Scale_Group_Log objectFilter - .. getLogs(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getLogs/ + + .. _SoftLayer_Scale_Group::getLogs(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getLogs/ """ return self.client.call('SoftLayer_Scale_Group', 'getLogs', id=identifier, mask=mask, filter=object_filter, iter=True) diff --git a/docs/api/managers/autoscale.rst b/docs/api/managers/autoscale.rst new file mode 100644 index 000000000..3a617c244 --- /dev/null +++ b/docs/api/managers/autoscale.rst @@ -0,0 +1,5 @@ +.. _autoscale: + +.. automodule:: SoftLayer.managers.autoscale + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst new file mode 100644 index 000000000..8d4d83a34 --- /dev/null +++ b/docs/cli/autoscale.rst @@ -0,0 +1,31 @@ +.. _cli_autoscale: + +Autoscale Commands +================== +These commands were added in version `5.8.1 `_ + +For making changes to the triggers or the autoscale group itself, see the `Autoscale Portal`_ + +- `Autoscale Product `_ +- `Autoscale Documentation `_ +- `Autoscale Portal`_ + +.. click:: SoftLayer.CLI.autoscale.list:cli + :prog: autoscale list + :show-nested: + +.. click:: SoftLayer.CLI.autoscale.detail:cli + :prog: autoscale detail + :show-nested: + +.. click:: SoftLayer.CLI.autoscale.scale:cli + :prog: autoscale scale + :show-nested: + +.. click:: SoftLayer.CLI.autoscale.logs:cli + :prog: autoscale logs + :show-nested: + + + +.. _Autoscale Portal: https://cloud.ibm.com/classic/autoscale \ No newline at end of file From eed15b03830bc1496814b7adcfa32ed1eec5264e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 27 Sep 2019 17:05:02 -0500 Subject: [PATCH 0171/1582] tox fixes --- SoftLayer/managers/autoscale.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 7f5d3d321..a5aeeca79 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -18,7 +18,7 @@ def list(self, mask=None): :param mask: optional SoftLayer_Scale_Group objectMask - .. _SoftLayer_Account::getScaleGroups(): + .. _SoftLayer_Account::getScaleGroups(): https://sldn.softlayer.com/reference/services/SoftLayer_Account/getScaleGroups/ """ if not mask: @@ -32,7 +32,7 @@ def details(self, identifier, mask=None): :param identifier: SoftLayer_Scale_Group id :param mask: optional SoftLayer_Scale_Group objectMask - .. _SoftLayer_Scale_Group::getObject(): + .. _SoftLayer_Scale_Group::getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/ """ if not mask: @@ -49,7 +49,7 @@ def get_policy(self, identifier, mask=None): :param identifier: SoftLayer_Scale_Policy id :param mask: optional SoftLayer_Scale_Policy objectMask - .. _SoftLayer_Scale_Policy::getObject(): + .. _SoftLayer_Scale_Policy::getObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Policy/getObject/ """ if not mask: @@ -65,7 +65,7 @@ def scale(self, identifier, amount): :param identifier: SoftLayer_Scale_Group Id :param amount: positive or negative number to scale the group by - .. _SoftLayer_Scale_Group::scale(): + .. _SoftLayer_Scale_Group::scale(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scale/ """ return self.client.call('SoftLayer_Scale_Group', 'scale', amount, id=identifier) @@ -76,7 +76,7 @@ def scale_to(self, identifier, amount): :param identifier: SoftLayer_Scale_Group Id :param amount: number to scale the group to. - .. _SoftLayer_Scale_Group::scaleTo(): + .. _SoftLayer_Scale_Group::scaleTo(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/scaleTo/ """ return self.client.call('SoftLayer_Scale_Group', 'scaleTo', amount, id=identifier) @@ -88,7 +88,7 @@ def get_logs(self, identifier, mask=None, object_filter=None): :param mask: optional SoftLayer_Scale_Group_Log objectMask :param object_filter: optional SoftLayer_Scale_Group_Log objectFilter - .. _SoftLayer_Scale_Group::getLogs(): + .. _SoftLayer_Scale_Group::getLogs(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getLogs/ """ return self.client.call('SoftLayer_Scale_Group', 'getLogs', id=identifier, mask=mask, filter=object_filter, From 69e11f1f12c61f9bba25d70f37d81693d294dcd7 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Oct 2019 10:57:34 -0400 Subject: [PATCH 0172/1582] Autoscale cli list and managers unit test. --- SoftLayer/fixtures/SoftLayer_Account.py | 88 ++++++ SoftLayer/fixtures/SoftLayer_Scale_Group.py | 290 +++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Scale_Policy.py | 58 ++++ tests/CLI/modules/autoscale_tests.py | 14 + tests/managers/autoscale_tests.py | 95 ++++++ 5 files changed, 545 insertions(+) create mode 100644 SoftLayer/fixtures/SoftLayer_Scale_Group.py create mode 100644 SoftLayer/fixtures/SoftLayer_Scale_Policy.py create mode 100644 tests/CLI/modules/autoscale_tests.py create mode 100644 tests/managers/autoscale_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index a0c865e3d..ac0068c5e 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -769,3 +769,91 @@ } } ] + +getScaleGroups = [ + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2016-10-25T01:48:34+08:00", + "id": 12222222, + "lastActionDate": "2016-10-25T01:48:34+08:00", + "maximumMemberCount": 5, + "minimumMemberCount": 0, + "name": "tests", + "regionalGroupId": 663, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "sodg.com", + "hostname": "testing", + "id": None, + "maxCpu": None, + "maxMemory": 32768, + "startCpus": 32, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "sao01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_LATEST", + "privateNetworkOnlyFlag": True + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2018-04-24T04:22:00+08:00", + "id": 224533333, + "lastActionDate": "2019-01-19T04:53:18+08:00", + "maximumMemberCount": 10, + "minimumMemberCount": 0, + "modifyDate": "2019-01-19T04:53:21+08:00", + "name": "test-ajcb", + "regionalGroupId": 1025, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "test.local", + "hostname": "autoscale-ajcb01", + "id": None, + "maxCpu": None, + "maxMemory": 1024, + "postInstallScriptUri": "http://test.com", + "startCpus": 1, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "seo01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_7_64", + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, +] diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py new file mode 100644 index 000000000..0a30881cf --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -0,0 +1,290 @@ +getObject = { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2016-10-25T01:48:34+08:00", + "id": 12222222, + "lastActionDate": "2016-10-25T01:48:34+08:00", + "maximumMemberCount": 5, + "minimumMemberCount": 0, + "name": "tests", + "regionalGroupId": 663, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "sodg.com", + "hostname": "testing", + "id": None, + "maxCpu": None, + "maxMemory": 32768, + "startCpus": 32, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "sao01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_LATEST", + "privateNetworkOnlyFlag": True + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] +} + +scale = [ + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2016-10-25T01:48:34+08:00", + "id": 12222222, + "lastActionDate": "2016-10-25T01:48:34+08:00", + "maximumMemberCount": 5, + "minimumMemberCount": 0, + "name": "tests", + "regionalGroupId": 663, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "sodg.com", + "hostname": "testing", + "id": None, + "maxCpu": None, + "maxMemory": 32768, + "startCpus": 32, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "sao01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_LATEST", + "privateNetworkOnlyFlag": True + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2018-04-24T04:22:00+08:00", + "id": 224533333, + "lastActionDate": "2019-01-19T04:53:18+08:00", + "maximumMemberCount": 10, + "minimumMemberCount": 0, + "modifyDate": "2019-01-19T04:53:21+08:00", + "name": "test-ajcb", + "regionalGroupId": 1025, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "test.local", + "hostname": "autoscale-ajcb01", + "id": None, + "maxCpu": None, + "maxMemory": 1024, + "postInstallScriptUri": "http://test.com", + "startCpus": 1, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "seo01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_7_64", + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, +] + +scaleTo = [ + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2016-10-25T01:48:34+08:00", + "id": 12222222, + "lastActionDate": "2016-10-25T01:48:34+08:00", + "maximumMemberCount": 5, + "minimumMemberCount": 0, + "name": "tests", + "regionalGroupId": 663, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "sodg.com", + "hostname": "testing", + "id": None, + "maxCpu": None, + "maxMemory": 32768, + "startCpus": 32, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "sao01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_LATEST", + "privateNetworkOnlyFlag": True + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, + { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2018-04-24T04:22:00+08:00", + "id": 224533333, + "lastActionDate": "2019-01-19T04:53:18+08:00", + "maximumMemberCount": 10, + "minimumMemberCount": 0, + "modifyDate": "2019-01-19T04:53:21+08:00", + "name": "test-ajcb", + "regionalGroupId": 1025, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "test.local", + "hostname": "autoscale-ajcb01", + "id": None, + "maxCpu": None, + "maxMemory": 1024, + "postInstallScriptUri": "http://test.com", + "startCpus": 1, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + } + ], + "datacenter": { + "name": "seo01", + }, + "hourlyBillingFlag": True, + "operatingSystemReferenceCode": "CENTOS_7_64", + }, + "virtualGuestMemberCount": 0, + "status": { + "id": 1, + "keyName": "ACTIVE", + "name": "Active" + }, + "virtualGuestAssets": [], + "virtualGuestMembers": [] + }, +] + +getLogs = [ + { + "createDate": "2019-10-03T04:26:11+08:00", + "description": "Scaling group to 6 member(s) by adding 3 member(s) as manually requested", + "id": 3821111, + "scaleGroupId": 2252222, + "scaleGroup": { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2018-05-01T03:07:40+08:00", + "id": 2251111, + "lastActionDate": "2019-10-03T04:26:17+08:00", + "maximumMemberCount": 6, + "minimumMemberCount": 2, + "modifyDate": "2019-10-03T04:26:21+08:00", + "name": "ajcb-autoscale11", + "regionalGroupId": 663, + "terminationPolicyId": 2, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "techsupport.com", + "hostname": "ajcb-autoscale22", + "maxMemory": 1024, + "postInstallScriptUri": "https://pastebin.com/raw/62wrEKuW", + "startCpus": 1, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + }, + { + "device": "2", + "diskImage": { + "capacity": 10, + } + } + ], + "datacenter": { + "name": "sao01", + }, + "networkComponents": [ + { + "maxSpeed": 100, + } + ], + "operatingSystemReferenceCode": "CENTOS_LATEST", + "sshKeys": [ + { + "id": 49111, + } + ] + }, + "logs": [ + { + "createDate": "2019-09-28T02:31:35+08:00", + "description": "Scaling group to 3 member(s) by removing -1 member(s) as manually requested", + "id": 3821111, + "scaleGroupId": 2251111, + }, + { + "createDate": "2019-09-28T02:26:11+08:00", + "description": "Scaling group to 4 member(s) by adding 2 member(s) as manually requested", + "id": 38211111, + "scaleGroupId": 2251111, + }, + ] + } + }, +] diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Policy.py b/SoftLayer/fixtures/SoftLayer_Scale_Policy.py new file mode 100644 index 000000000..5586fec1f --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Scale_Policy.py @@ -0,0 +1,58 @@ +getObject = { + "cooldown": None, + "createDate": "2019-09-27T06:30:14+08:00", + "id": 11111, + "name": "prime-poly", + "scaleGroupId": 2255111, + "scaleGroup": { + "accountId": 31111, + "cooldown": 1800, + "createDate": "2018-05-01T03:07:40+08:00", + "id": 2252222, + "lastActionDate": "2019-09-28T02:31:47+08:00", + "maximumMemberCount": 6, + "minimumMemberCount": 2, + "modifyDate": "2019-09-28T02:31:50+08:00", + "name": "ajcb-autoscale11", + "regionalGroupId": 663, + "terminationPolicyId": 2, + "virtualGuestMemberTemplate": { + "accountId": 31111, + "domain": "techsupport.com", + "hostname": "ajcb-autoscale22", + "id": None, + "maxMemory": 1024, + "postInstallScriptUri": "https://pastebin.com/raw/62wrEKuW", + "startCpus": 1, + "blockDevices": [ + { + "device": "0", + "diskImage": { + "capacity": 25, + } + }, + { + "device": "2", + "diskImage": { + "capacity": 10, + } + } + ], + "datacenter": { + "id": None, + "name": "sao01", + }, + "networkComponents": [ + { + "maxSpeed": 100, + } + ], + "operatingSystemReferenceCode": "CENTOS_LATEST", + "sshKeys": [ + { + "id": 490279, + } + ] + } + } +} diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py new file mode 100644 index 000000000..33bf4a370 --- /dev/null +++ b/tests/CLI/modules/autoscale_tests.py @@ -0,0 +1,14 @@ +""" + SoftLayer.tests.CLI.modules.autoscale_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer import testing + + +class AutoScaleTests(testing.TestCase): + + def test_autoscale_list(self): + result = self.run_command(['autoscale', 'list']) + self.assert_no_fail(result) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py new file mode 100644 index 000000000..ba43cb42b --- /dev/null +++ b/tests/managers/autoscale_tests.py @@ -0,0 +1,95 @@ +""" + SoftLayer.tests.managers.autoscale_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer import testing +from SoftLayer.managers.autoscale import AutoScaleManager + + +class AutoScaleTests(testing.TestCase): + + def set_up(self): + self.autoscale = AutoScaleManager(self.client) + + def test_autoscale_list(self): + self.autoscale.list() + + self.assert_called_with( + 'SoftLayer_Account', + 'getScaleGroups' + ) + + def test_autoscale_list_with_mask(self): + self.autoscale.list(mask='mask[status,virtualGuestMemberCount]') + + self.assert_called_with( + 'SoftLayer_Account', + 'getScaleGroups' + ) + + def test_autoscale_details(self): + self.autoscale.details(11111) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'getObject', + identifier=11111 + ) + + def test_autoscale_details_with_mask(self): + self.autoscale.details(11111, mask='mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], ' + 'terminationPolicy,virtualGuestMemberCount]') + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'getObject', + identifier=11111 + ) + + def test_autoscale_policy(self): + self.autoscale.get_policy(11111) + + self.assert_called_with( + 'SoftLayer_Scale_Policy', + 'getObject', + identifier=11111 + ) + + def test_autoscale_policy_with_mask(self): + self.autoscale.get_policy(11111, mask='mask[cooldown, createDate, id, name, actions, triggers[type]]') + + self.assert_called_with( + 'SoftLayer_Scale_Policy', + 'getObject', + identifier=11111 + ) + + def test_autoscale_scale(self): + self.autoscale.scale(11111, 3) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'scale', + identifier=11111 + ) + + def test_autoscale_scaleTo(self): + self.autoscale.scale_to(11111, 3) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'scaleTo', + identifier=11111 + ) + + def test_autoscale_getLogs(self): + self.autoscale.get_logs(11111) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'getLogs', + identifier=11111 + ) From 2fcd61beab21ff350a6e675d223b785b387230a4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Oct 2019 11:07:02 -0400 Subject: [PATCH 0173/1582] Fix analysis tox. --- tests/managers/autoscale_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index ba43cb42b..124a82874 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -5,8 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import testing from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer import testing class AutoScaleTests(testing.TestCase): From 3f6f16cd9a1e5637ceefcca03cd9f6c24e35cbd3 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Oct 2019 14:35:03 -0400 Subject: [PATCH 0174/1582] Fix analysis tox. --- SoftLayer/fixtures/SoftLayer_Account.py | 9 ++------- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 14 -------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ac0068c5e..ddb2a4354 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -802,7 +802,6 @@ }, "hourlyBillingFlag": True, "operatingSystemReferenceCode": "CENTOS_LATEST", - "privateNetworkOnlyFlag": True }, "virtualGuestMemberCount": 0, "status": { @@ -810,8 +809,6 @@ "keyName": "ACTIVE", "name": "Active" }, - "virtualGuestAssets": [], - "virtualGuestMembers": [] }, { "accountId": 31111, @@ -852,8 +849,6 @@ "id": 1, "keyName": "ACTIVE", "name": "Active" - }, - "virtualGuestAssets": [], - "virtualGuestMembers": [] - }, + } + } ] diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index 0a30881cf..403bd5203 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -47,17 +47,13 @@ "cooldown": 1800, "createDate": "2016-10-25T01:48:34+08:00", "id": 12222222, - "lastActionDate": "2016-10-25T01:48:34+08:00", "maximumMemberCount": 5, "minimumMemberCount": 0, "name": "tests", - "regionalGroupId": 663, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "sodg.com", "hostname": "testing", - "id": None, - "maxCpu": None, "maxMemory": 32768, "startCpus": 32, "blockDevices": [ @@ -89,12 +85,10 @@ "cooldown": 1800, "createDate": "2018-04-24T04:22:00+08:00", "id": 224533333, - "lastActionDate": "2019-01-19T04:53:18+08:00", "maximumMemberCount": 10, "minimumMemberCount": 0, "modifyDate": "2019-01-19T04:53:21+08:00", "name": "test-ajcb", - "regionalGroupId": 1025, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "test.local", @@ -148,14 +142,6 @@ "maxCpu": None, "maxMemory": 32768, "startCpus": 32, - "blockDevices": [ - { - "device": "0", - "diskImage": { - "capacity": 25, - } - } - ], "datacenter": { "name": "sao01", }, From d027f82fdc68b4b709c75c82882a3b5c9d5c9d40 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 3 Oct 2019 15:54:11 -0400 Subject: [PATCH 0175/1582] unit test logs and scale --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 12 ++++++++++++ tests/CLI/modules/autoscale_tests.py | 0 2 files changed, 12 insertions(+) create mode 100644 SoftLayer/fixtures/SoftLayer_Scale_Group.py create mode 100644 tests/CLI/modules/autoscale_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py new file mode 100644 index 000000000..a4d885e97 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -0,0 +1,12 @@ +getLogs = [ + { + "createDate": "2018-04-23T14:22:52-06:00", + "description": "Scaling group to 3 member(s) by adding 3 member(s) as manually requested", + "id": 123456, + }, { + "createDate": "2018-04-23T14:22:52-06:00", + "description": "Scaling group to 3 member(s) by adding 3 member(s) as manually requested", + "id": 987123456, + } +] + diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py new file mode 100644 index 000000000..e69de29bb From 292d4fac6d77d54904ae6c5d79fca0894c1f1f27 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 3 Oct 2019 15:59:30 -0400 Subject: [PATCH 0176/1582] unit test logs and scale --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 53 ++++++++++++++++++--- tests/CLI/modules/autoscale_tests.py | 35 ++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index a4d885e97..b53e08ff1 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -1,12 +1,53 @@ getLogs = [ { - "createDate": "2018-04-23T14:22:52-06:00", - "description": "Scaling group to 3 member(s) by adding 3 member(s) as manually requested", - "id": 123456, + "createDate": "2019-10-02T08:34:21-06:00", + "id": 3145330, + "scaleGroupId": 224541 }, { - "createDate": "2018-04-23T14:22:52-06:00", - "description": "Scaling group to 3 member(s) by adding 3 member(s) as manually requested", - "id": 987123456, + "createDate": "2019-10-02T08:34:21-06:00", + "id": 3145330, + "scaleGroupId": 2245415 + } ] +scale=[] + +scaleTo=[{'createDate': '2019-10-02T15:24:56-06:00', + 'id': 3145162, + 'scaleGroupId': 595465, + 'scaleGroup': {}, + 'virtualGuest': { + 'accountId': 12369852, + 'createDate': '2019 - 10 - 02T15: 24:54 - 06: 00', + 'billingItem': { + "cancellationDate": '2019-10-02T08:34:21-06:00' + }, + 'domain': 'mesos.maceacs.com', + 'fullyQualifiedDomainName': '2 - master - 600e.mesos.maceacs.com', + 'hostname': '2 - master - 600e', + 'id': 852369, + 'maxCpu': 4, + 'maxCpuUnits': 'CORE', + 'maxMemory': 8192, + 'startCpus': 4, + 'statusId': 1001, + 'typeId': 1, + 'uuid': 'acva18321 - bb6b - 4861 - 87f3 - 8a6dbbcf148b', + 'activeTransactions': [{ + 'createDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'elapsedSeconds': 2, + 'guestId': 789654123, + 'hardwareId': '', + 'id': 456987, + 'modifyDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'statusChangeDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'transactionStatus': { + 'averageDuration': '1.48', + 'friendlyName': 'AssignHost', + 'name': 'ASSIGN_HOST'}}], + 'status': { + 'keyName': 'ACTIVE', + 'name': 'Active'} + } + }] diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index e69de29bb..faca1fb62 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -0,0 +1,35 @@ +""" + SoftLayer.tests.CLI.modules.autoscale_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import testing + + +class AutoscaleTests(testing.TestCase): + + def test_logs_dates(self): + result = self.run_command(['autoscale', 'logs', '123456', '-d', '2019-02-02']) + print(result) + self.assert_no_fail(result) + + def test_scale_down(self): + result = self.run_command(['autoscale', 'scale', '123456', '--down', '--amount', '2']) + self.assert_no_fail(result) + + def test_scale_up(self): + result = self.run_command(['autoscale', 'scale', '123456', '--up', '--amount', '2']) + self.assert_no_fail(result) + + def test_scale_to(self): + result = self.run_command(['autoscale', 'scale', '789654123', '--down', '--amount', '2']) + self.assert_no_fail(result) + + def test_scale_by_up(self): + result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '-1']) + self.assert_no_fail(result) + + def test_scale_ca(self): + result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '1']) + self.assert_no_fail(result) From 5cc773755a89105940fb352b7f70a7422e689b0e Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 3 Oct 2019 16:08:49 -0400 Subject: [PATCH 0177/1582] fix the tox analysis --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 69 +++++++++++---------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index b53e08ff1..9fc9b0483 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -11,43 +11,44 @@ } ] -scale=[] +scale = [] -scaleTo=[{'createDate': '2019-10-02T15:24:56-06:00', +scaleTo = [{'createDate': '2019-10-02T15:24:56-06:00', 'id': 3145162, 'scaleGroupId': 595465, 'scaleGroup': {}, 'virtualGuest': { 'accountId': 12369852, - 'createDate': '2019 - 10 - 02T15: 24:54 - 06: 00', - 'billingItem': { - "cancellationDate": '2019-10-02T08:34:21-06:00' - }, - 'domain': 'mesos.maceacs.com', - 'fullyQualifiedDomainName': '2 - master - 600e.mesos.maceacs.com', - 'hostname': '2 - master - 600e', - 'id': 852369, - 'maxCpu': 4, - 'maxCpuUnits': 'CORE', - 'maxMemory': 8192, - 'startCpus': 4, - 'statusId': 1001, - 'typeId': 1, - 'uuid': 'acva18321 - bb6b - 4861 - 87f3 - 8a6dbbcf148b', - 'activeTransactions': [{ - 'createDate': '2019 - 10 - 02T15: 24:58 - 06: 00', - 'elapsedSeconds': 2, - 'guestId': 789654123, - 'hardwareId': '', - 'id': 456987, - 'modifyDate': '2019 - 10 - 02T15: 24:58 - 06: 00', - 'statusChangeDate': '2019 - 10 - 02T15: 24:58 - 06: 00', - 'transactionStatus': { - 'averageDuration': '1.48', - 'friendlyName': 'AssignHost', - 'name': 'ASSIGN_HOST'}}], - 'status': { - 'keyName': 'ACTIVE', - 'name': 'Active'} - } - }] + 'createDate': '2019 - 10 - 02T15: 24:54 - 06: 00', + 'billingItem': { + "cancellationDate": '2019-10-02T08:34:21-06:00' + }, + 'domain': 'mesos.maceacs.com', + 'fullyQualifiedDomainName': '2 - master - 600e.mesos.maceacs.com', + 'hostname': '2 - master - 600e', + 'id': 852369, + 'maxCpu': 4, + 'maxCpuUnits': 'CORE', + 'maxMemory': 8192, + 'startCpus': 4, + 'statusId': 1001, + 'typeId': 1, + 'uuid': 'acva18321 - bb6b - 4861 - 87f3 - 8a6dbbcf148b', + 'activeTransactions': [{ + 'createDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'elapsedSeconds': 2, + 'guestId': 789654123, + 'hardwareId': '', + 'id': 456987, + 'modifyDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'statusChangeDate': '2019 - 10 - 02T15: 24:58 - 06: 00', + 'transactionStatus': { + 'averageDuration': '1.48', + 'friendlyName': 'AssignHost', + 'name': 'ASSIGN_HOST'}}], + 'status': { + 'keyName': 'ACTIVE', + 'name': 'Active'} + } + } + ] From 50566503803be41b0aa8a618835876fe33bc6a57 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Oct 2019 10:39:51 -0400 Subject: [PATCH 0178/1582] fix any problems with merge --- tests/CLI/modules/autoscale_tests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6186cfcb3..843ed4528 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -1,19 +1,15 @@ """ SoftLayer.tests.CLI.modules.autoscale_tests -<<<<<<< HEAD ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. -======= ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Tests for the user cli command ->>>>>>> 63de0adb45c6c1ccc7135520ebcdaeb38249e9fa """ from SoftLayer import testing -<<<<<<< HEAD class AutoscaleTests(testing.TestCase): def test_logs_dates(self): @@ -39,10 +35,7 @@ def test_scale_by_up(self): def test_scale_ca(self): result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '1']) -======= -class AutoScaleTests(testing.TestCase): def test_autoscale_list(self): result = self.run_command(['autoscale', 'list']) ->>>>>>> 63de0adb45c6c1ccc7135520ebcdaeb38249e9fa self.assert_no_fail(result) From 5e61352a7106919efd0bbd57dbdfe610c44d9eda Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 4 Oct 2019 11:45:24 -0400 Subject: [PATCH 0179/1582] adding a default value to dictionaries --- SoftLayer/CLI/autoscale/detail.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py index 88fec1c18..d7484184c 100644 --- a/SoftLayer/CLI/autoscale/detail.py +++ b/SoftLayer/CLI/autoscale/detail.py @@ -34,7 +34,7 @@ def cli(env, identifier): table.add_row(['Cooldown', "{} seconds".format(group.get('cooldown'))]) table.add_row(['Last Action', utils.clean_time(group.get('lastActionDate'))]) - for network in group.get('networkVlans'): + for network in group.get('networkVlans', []): network_type = utils.lookup(network, 'networkVlan', 'networkSpace') router = utils.lookup(network, 'networkVlan', 'primaryRouter', 'hostname') vlan_number = utils.lookup(network, 'networkVlan', 'vlanNumber') @@ -55,17 +55,16 @@ def cli(env, identifier): config_table.add_row(['Core', template.get('startCpus')]) config_table.add_row(['Ram', template.get('maxMemory')]) network = template.get('networkComponents') - config_table.add_row(['Network', network[0]['maxSpeed']]) + config_table.add_row(['Network', network[0]['maxSpeed'] if network else 'Default']) ssh_keys = template.get('sshKeys', []) ssh_manager = SoftLayer.SshKeyManager(env.client) for key in ssh_keys: # Label isn't included when retrieved from the AutoScale group... ssh_key = ssh_manager.get_key(key.get('id')) config_table.add_row(['SSH Key {}'.format(ssh_key.get('id')), ssh_key.get('label')]) - disks = template.get('blockDevices') - disk_type = "SAN" - if template.get('localDiskFlag'): - disk_type = "Local" + disks = template.get('blockDevices', []) + disk_type = "Local" if template.get('localDiskFlag') else "SAN" + for disk in disks: disk_image = disk.get('diskImage') config_table.add_row(['{} Disk {}'.format(disk_type, disk.get('device')), disk_image.get('capacity')]) @@ -76,7 +75,7 @@ def cli(env, identifier): # Policy Config Table policy_table = formatting.KeyValueTable(["Policy", "Cooldown"]) - policies = group.get('policies') + policies = group.get('policies', []) for policy in policies: policy_table.add_row([policy.get('name'), policy.get('cooldown') or group.get('cooldown')]) @@ -84,7 +83,7 @@ def cli(env, identifier): # Active Guests member_table = formatting.Table(['Id', 'Hostname', 'Created'], title="Active Guests") - guests = group.get('virtualGuestMembers') + guests = group.get('virtualGuestMembers', []) for guest in guests: real_guest = guest.get('virtualGuest') member_table.add_row([ From 29ab911aa399085aad81da8a7ea95997a2afb359 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 4 Oct 2019 12:23:55 -0400 Subject: [PATCH 0180/1582] added autoscale detail test --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 224 +++++++++++++++++--- tests/CLI/modules/autoscale_tests.py | 6 +- 2 files changed, 195 insertions(+), 35 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index 403bd5203..5afda75a7 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -1,44 +1,200 @@ getObject = { - "accountId": 31111, - "cooldown": 1800, - "createDate": "2016-10-25T01:48:34+08:00", - "id": 12222222, - "lastActionDate": "2016-10-25T01:48:34+08:00", - "maximumMemberCount": 5, - "minimumMemberCount": 0, - "name": "tests", - "regionalGroupId": 663, - "virtualGuestMemberTemplate": { - "accountId": 31111, - "domain": "sodg.com", - "hostname": "testing", - "id": None, - "maxCpu": None, - "maxMemory": 32768, - "startCpus": 32, - "blockDevices": [ + 'accountId': 31111, + 'balancedTerminationFlag': False, + 'cooldown': 1800, + 'createDate': '2018-04-30T15:07:40-04:00', + 'desiredMemberCount': None, + 'id': 12222222, + 'lastActionDate': '2019-10-02T16:26:17-04:00', + 'loadBalancers': [], + 'maximumMemberCount': 6, + 'minimumMemberCount': 2, + 'modifyDate': '2019-10-03T17:16:47-04:00', + 'name': 'tests', + 'networkVlans': [ + { + 'networkVlan': { + 'accountId': 31111, + 'id': 2222222, + 'modifyDate': '2019-07-16T13:09:47-04:00', + 'networkSpace': 'PRIVATE', + 'primaryRouter': { + 'hostname': 'bcr01a.sao01' + }, + 'primarySubnetId': 1172222, + 'vlanNumber': 1111 + }, + 'networkVlanId': 2281111 + } + ], + 'policies': [ + {'actions': [ { - "device": "0", - "diskImage": { - "capacity": 25, + 'amount': 1, + 'createDate': '2019-09-26T18:30:22-04:00', + 'deleteFlag': None, + 'id': 611111, + 'modifyDate': None, + 'scalePolicy': None, + 'scalePolicyId': 681111, + 'scaleType': 'RELATIVE', + 'typeId': 1 + } + ], + 'cooldown': None, + 'createDate': '2019-09-26T18:30:14-04:00', + 'id': 680000, + 'name': 'prime-poly', + 'scaleActions': [ + { + 'amount': 1, + 'createDate': '2019-09-26T18:30:22-04:00', + 'deleteFlag': None, + 'id': 633333, + 'modifyDate': None, + 'scalePolicy': None, + 'scalePolicyId': 680123, + 'scaleType': 'RELATIVE', + 'typeId': 1 } + ], + 'triggers': [ + { + 'createDate': '2019-09-26T18:30:14-04:00', + 'deleteFlag': None, + 'id': 557111, + 'modifyDate': None, + 'scalePolicy': None, + 'scalePolicyId': 680000, + 'typeId': 3 + } + ] + } + ], + 'regionalGroup': { + 'description': 'sa-bra-south-1', + 'id': 663, + 'locationGroupTypeId': 42, + 'locations': [ + { + 'id': 983497, + 'longName': 'Sao Paulo 1', + 'name': 'sao01', + 'statusId': 2 } ], - "datacenter": { - "name": "sao01", - }, - "hourlyBillingFlag": True, - "operatingSystemReferenceCode": "CENTOS_LATEST", - "privateNetworkOnlyFlag": True + 'name': 'sa-bra-south-1', + 'securityLevelId': None + }, + 'regionalGroupId': 663, + 'status': { + 'id': 1, 'keyName': 'ACTIVE', 'name': 'Active' }, - "virtualGuestMemberCount": 0, - "status": { - "id": 1, - "keyName": "ACTIVE", - "name": "Active" + 'suspendedFlag': False, + 'terminationPolicy': { + 'id': 2, 'keyName': 'NEWEST', 'name': 'Newest' }, - "virtualGuestAssets": [], - "virtualGuestMembers": [] + 'terminationPolicyId': 2, + 'virtualGuestAssets': [], + 'virtualGuestMemberCount': 6, + 'virtualGuestMemberTemplate': { + 'accountId': 31111, + 'blockDevices': [ + { + 'bootableFlag': None, + 'createDate': None, + 'device': '0', + 'diskImage': { + 'capacity': 25, + 'createDate': None, + 'id': None, + 'modifyDate': None, + 'parentId': None, + 'storageRepositoryId': None, + 'typeId': None}, + 'diskImageId': None, + 'guestId': None, + 'hotPlugFlag': None, + 'id': None, + 'modifyDate': None, + 'statusId': None + }, + { + 'bootableFlag': None, + 'createDate': None, + 'device': '2', + 'diskImage': { + 'capacity': 10, + 'createDate': None, + 'id': None, + 'modifyDate': None, + 'parentId': None, + 'storageRepositoryId': None, + 'typeId': None}, + 'diskImageId': None, + 'guestId': None, + 'hotPlugFlag': None, + 'id': None, + 'modifyDate': None, + 'statusId': None + } + ], + 'createDate': None, + 'datacenter': { + 'id': None, + 'name': 'sao01', + 'statusId': None + }, + 'dedicatedAccountHostOnlyFlag': None, + 'domain': 'tech-support.com', + 'hostname': 'testing', + 'hourlyBillingFlag': True, + 'id': None, + 'lastPowerStateId': None, + 'lastVerifiedDate': None, + 'localDiskFlag': False, + 'maxCpu': None, + 'maxMemory': 1024, + 'metricPollDate': None, + 'modifyDate': None, + 'networkComponents': [ + { + 'createDate': None, + 'guestId': None, + 'id': None, + 'maxSpeed': 100, + 'modifyDate': None, + 'networkId': None, + 'port': None, + 'speed': None + } + ], + 'operatingSystemReferenceCode': 'CENTOS_LATEST', + 'placementGroupId': None, + 'postInstallScriptUri': 'https://test.com/', + 'privateNetworkOnlyFlag': False, + 'provisionDate': None, + 'sshKeys': [ + { + 'createDate': None, + 'id': 490279, + 'modifyDate': None + } + ], + 'startCpus': 1, + 'statusId': None, + 'typeId': None}, + 'virtualGuestMembers': [ + { + 'id': 3111111, + 'virtualGuest': { + + 'domain': 'tech-support.com', + 'hostname': 'test', + 'provisionDate': '2019-09-27T14:29:53-04:00' + } + } + ] } scale = [ diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 33bf4a370..0c0ae92e0 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -2,7 +2,7 @@ SoftLayer.tests.CLI.modules.autoscale_tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Tests for the user cli command + Tests for the autoscale cli command """ from SoftLayer import testing @@ -12,3 +12,7 @@ class AutoScaleTests(testing.TestCase): def test_autoscale_list(self): result = self.run_command(['autoscale', 'list']) self.assert_no_fail(result) + + def test_autoscale_detail(self): + result = self.run_command(['autoscale', 'detail', '12222222']) + self.assert_no_fail(result) From 52d3600dd995371b4a3b0c4ba82e8bbe053e5b00 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 4 Oct 2019 12:42:12 -0400 Subject: [PATCH 0181/1582] fix identation --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 27 +++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index 5afda75a7..b2361784a 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -28,19 +28,20 @@ } ], 'policies': [ - {'actions': [ - { - 'amount': 1, - 'createDate': '2019-09-26T18:30:22-04:00', - 'deleteFlag': None, - 'id': 611111, - 'modifyDate': None, - 'scalePolicy': None, - 'scalePolicyId': 681111, - 'scaleType': 'RELATIVE', - 'typeId': 1 - } - ], + { + 'actions': [ + { + 'amount': 1, + 'createDate': '2019-09-26T18:30:22-04:00', + 'deleteFlag': None, + 'id': 611111, + 'modifyDate': None, + 'scalePolicy': None, + 'scalePolicyId': 681111, + 'scaleType': 'RELATIVE', + 'typeId': 1 + } + ], 'cooldown': None, 'createDate': '2019-09-26T18:30:14-04:00', 'id': 680000, From 3e853199e7b7af63d79259c80318b28f98836238 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Oct 2019 15:22:15 -0400 Subject: [PATCH 0182/1582] Update SoftLayer_Scale_Group.py --- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 22 ++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index 403bd5203..afaaf118b 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -50,6 +50,12 @@ "maximumMemberCount": 5, "minimumMemberCount": 0, "name": "tests", + "virtualGuest": { + "accountId": 31111, + "createDate": "2019-10-02T15:24:54-06:00", + "billingItem": { + "cancellationDate": "2019-10-02T08:34:21-06:00"} + }, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "sodg.com", @@ -89,6 +95,12 @@ "minimumMemberCount": 0, "modifyDate": "2019-01-19T04:53:21+08:00", "name": "test-ajcb", + "virtualGuest": { + "accountId": 31111, + "createDate": "2019-10-02T15:24:54-06:00", + "billingItem": { + "cancellationDate": "2019-10-02T08:34:21-06:00"} + }, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "test.local", @@ -120,7 +132,7 @@ }, "virtualGuestAssets": [], "virtualGuestMembers": [] - }, + } ] scaleTo = [ @@ -134,6 +146,8 @@ "minimumMemberCount": 0, "name": "tests", "regionalGroupId": 663, + "virtualGuest": { + }, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "sodg.com", @@ -169,6 +183,12 @@ "modifyDate": "2019-01-19T04:53:21+08:00", "name": "test-ajcb", "regionalGroupId": 1025, + "virtualGuest": { + "accountId": 31111, + "createDate": "2019-10-02T15:24:54-06:00", + "billingItem": { + "cancellationDate": "2019-10-02T08:34:21-06:00"} + }, "virtualGuestMemberTemplate": { "accountId": 31111, "domain": "test.local", From 3dcf3f6a8a25675bbb8417b68755ec3275669c3d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Oct 2019 15:57:32 -0400 Subject: [PATCH 0183/1582] fix analysis tool --- tests/CLI/modules/autoscale_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 843ed4528..b3d7293c9 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -33,8 +33,9 @@ def test_scale_by_up(self): result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '-1']) self.assert_no_fail(result) - def test_scale_ca(self): + def test_scale_cancel(self): result = self.run_command(['autoscale', 'scale', '789654123', '--by', '--down', '--amount', '1']) + self.assert_no_fail(result) def test_autoscale_list(self): result = self.run_command(['autoscale', 'list']) From 58b27c6bf5400a717acd00b7866964ef11f36e59 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 4 Oct 2019 17:35:43 -0500 Subject: [PATCH 0184/1582] #627 added autoscale tag, to allow users to tag all guests in a group at once --- SoftLayer/CLI/autoscale/detail.py | 2 +- SoftLayer/CLI/autoscale/edit.py | 27 ++++++++++++++++ SoftLayer/CLI/autoscale/tag.py | 34 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ SoftLayer/fixtures/SoftLayer_Scale_Group.py | 2 ++ SoftLayer/managers/autoscale.py | 12 +++++++- docs/cli/autoscale.rst | 7 +++++ tests/CLI/modules/autoscale_tests.py | 8 +++++ tests/managers/autoscale_tests.py | 21 +++++++++++++ 9 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/autoscale/edit.py create mode 100644 SoftLayer/CLI/autoscale/tag.py diff --git a/SoftLayer/CLI/autoscale/detail.py b/SoftLayer/CLI/autoscale/detail.py index d7484184c..337be43a6 100644 --- a/SoftLayer/CLI/autoscale/detail.py +++ b/SoftLayer/CLI/autoscale/detail.py @@ -87,6 +87,6 @@ def cli(env, identifier): for guest in guests: real_guest = guest.get('virtualGuest') member_table.add_row([ - guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('provisionDate')) + real_guest.get('id'), real_guest.get('hostname'), utils.clean_time(real_guest.get('provisionDate')) ]) env.fout(member_table) diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py new file mode 100644 index 000000000..644316e72 --- /dev/null +++ b/SoftLayer/CLI/autoscale/edit.py @@ -0,0 +1,27 @@ +"""Edits an Autoscale group.""" +# :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') +# Suspend / Unsuspend +# Name +# Min/Max +# template->userData +# 'hostname', 'domain', 'startCpus', 'maxMemory', 'localDiskFlag' +# 'blockDeviceTemplateGroup.globalIdentifier +# blockDevices.diskImage.capacity +# sshKeys.id +# postInstallScriptUri +@environment.pass_env +def cli(env, identifier): + """Edits an Autoscale group.""" + + autoscale = AutoScaleManager(env.client) + groups = autoscale.details(identifier) + click.echo(groups) diff --git a/SoftLayer/CLI/autoscale/tag.py b/SoftLayer/CLI/autoscale/tag.py new file mode 100644 index 000000000..e1fe1745b --- /dev/null +++ b/SoftLayer/CLI/autoscale/tag.py @@ -0,0 +1,34 @@ +"""Tags all guests in an autoscale group.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.managers.autoscale import AutoScaleManager +from SoftLayer.managers.vs import VSManager + + +@click.command() +@click.argument('identifier') +@click.option('--tags', '-g', help="Tags to set for each guest in this group. Existing tags are overwritten. " + "An empty string will remove all tags") +@environment.pass_env +def cli(env, identifier, tags): + """Tags all guests in an autoscale group. + + --tags "Use, quotes, if you, want whitespace" + + --tags Otherwise,Just,commas + """ + + autoscale = AutoScaleManager(env.client) + vsmanager = VSManager(env.client) + mask = "mask[id,virtualGuestId,virtualGuest[tagReferences,id,hostname]]" + guests = autoscale.get_virtual_guests(identifier, mask=mask) + click.echo("New Tags: {}".format(tags)) + for guest in guests: + real_guest = guest.get('virtualGuest') + click.echo("Setting tags for {}".format(real_guest.get('hostname'))) + vsmanager.set_tags(tags, real_guest.get('id'),) + click.echo("Done") + # pp(guests) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 0c47ce90d..5c37541df 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -308,6 +308,8 @@ ('autoscale:detail', 'SoftLayer.CLI.autoscale.detail:cli'), ('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') ] ALL_ALIASES = { diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index 7e9325ff2..f9da7ff68 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -198,6 +198,8 @@ ] } +getVirtualGuestMembers = getObject['virtualGuestMembers'] + scale = [ { "accountId": 31111, diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index a5aeeca79..5298b05c6 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -36,7 +36,7 @@ def details(self, identifier, mask=None): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getObject/ """ if not mask: - mask = """mask[virtualGuestMembers[id,virtualGuest[hostname,domain,provisionDate]], terminationPolicy, + mask = """mask[virtualGuestMembers[id,virtualGuest[id,hostname,domain,provisionDate]], terminationPolicy, virtualGuestMemberCount, virtualGuestMemberTemplate[sshKeys], policies[id,name,createDate,cooldown,actions,triggers,scaleActions], networkVlans[networkVlanId,networkVlan[networkSpace,primaryRouter[hostname]]], @@ -93,3 +93,13 @@ def get_logs(self, identifier, mask=None, object_filter=None): """ return self.client.call('SoftLayer_Scale_Group', 'getLogs', id=identifier, mask=mask, filter=object_filter, iter=True) + + def get_virtual_guests(self, identifier, mask=None): + """Calls `SoftLayer_Scale_Group::getVirtualGuestMembers()`_ + + :param identifier: SoftLayer_Scale_Group Id + :param mask: optional SoftLayer_Scale_Member objectMask + .. _SoftLayer_Scale_Group::getVirtualGuestMembers(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getVirtualGuestMembers/ + """ + return self.client.call('SoftLayer_Scale_Group', 'getVirtualGuestMembers', id=identifier, mask=mask, iter=True) diff --git a/docs/cli/autoscale.rst b/docs/cli/autoscale.rst index 8d4d83a34..a3aa31462 100644 --- a/docs/cli/autoscale.rst +++ b/docs/cli/autoscale.rst @@ -26,6 +26,13 @@ For making changes to the triggers or the autoscale group itself, see the `Autos :prog: autoscale logs :show-nested: +.. click:: SoftLayer.CLI.autoscale.tag:cli + :prog: autoscale tag + :show-nested: + +.. click:: SoftLayer.CLI.autoscale.edit:cli + :prog: autoscale edit + :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 ed55f6999..de686a585 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -44,3 +44,11 @@ def test_autoscale_list(self): def test_autoscale_detail(self): result = self.run_command(['autoscale', 'detail', '12222222']) self.assert_no_fail(result) + + def test_autoscale_tag(self): + result = self.run_command(['autoscale', 'tag', '12345']) + self.assert_no_fail(result) + + def test_autoscale_edit(self): + result = self.run_command(['autoscale', 'edit', '12345']) + self.assert_no_fail(result) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 124a82874..9826778b9 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -93,3 +93,24 @@ def test_autoscale_getLogs(self): 'getLogs', identifier=11111 ) + + def test_autoscale_get_virtual_guests(self): + self.autoscale.get_virtual_guests(11111) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'getVirtualGuestMembers', + identifier=11111, + mask=None + ) + + def test_autoscale_get_virtual_guests_mask(self): + test_mask = "mask[id]" + self.autoscale.get_virtual_guests(11111, mask=test_mask) + + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'getVirtualGuestMembers', + identifier=11111, + mask=test_mask + ) From 878db94afff463cd3a200c382eb11fafab8588d0 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Tue, 8 Oct 2019 18:29:56 -0500 Subject: [PATCH 0185/1582] Edit autoscale groups command --- SoftLayer/CLI/autoscale/edit.py | 56 ++++++++++++++++----- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 2 + SoftLayer/managers/autoscale.py | 13 +++++ tests/CLI/modules/autoscale_tests.py | 34 ++++++++++++- tests/managers/autoscale_tests.py | 9 ++++ 5 files changed, 99 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py index 644316e72..ef9b610d9 100644 --- a/SoftLayer/CLI/autoscale/edit.py +++ b/SoftLayer/CLI/autoscale/edit.py @@ -9,19 +9,49 @@ @click.command() @click.argument('identifier') -# Suspend / Unsuspend -# Name -# Min/Max -# template->userData -# 'hostname', 'domain', 'startCpus', 'maxMemory', 'localDiskFlag' -# 'blockDeviceTemplateGroup.globalIdentifier -# blockDevices.diskImage.capacity -# sshKeys.id -# postInstallScriptUri -@environment.pass_env -def cli(env, identifier): +@click.option('--name', help="Scale group's name.") +@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('--userdata', help="User defined metadata string") +@click.option('--userfile', '-F', help="Read userdata from a file", + type=click.Path(exists=True, readable=True, resolve_path=True)) +@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") +@environment.pass_env +def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory): """Edits an Autoscale group.""" + template = {} autoscale = AutoScaleManager(env.client) - groups = autoscale.details(identifier) - click.echo(groups) + group = autoscale.details(identifier) + + template['name'] = name + template['minimumMemberCount'] = minimum + template['maximumMemberCount'] = maximum + virt_template = {} + if userdata: + virt_template['userData'] = [{"value":userdata}] + elif userfile: + with open(userfile, 'r') as userfile_obj: + virt_template['userData'] = [{"value":userfile_obj.read()}] + virt_template['startCpus'] = cpu + virt_template['maxMemory'] = memory + + # Remove any entries that are `None` as the API will complain about them. + template['virtualGuestMemberTemplate'] = clean_dict(virt_template) + clean_template = clean_dict(template) + + # If there are any values edited in the template, we need to get the OLD template values and replace them. + if template['virtualGuestMemberTemplate']: + # Update old template with new values + for key, value in clean_template['virtualGuestMemberTemplate'].items(): + group['virtualGuestMemberTemplate'][key] = value + clean_template['virtualGuestMemberTemplate'] = group['virtualGuestMemberTemplate'] + + result = autoscale.edit(identifier, clean_template) + click.echo("Done") + + +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/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index f9da7ff68..a6a666bde 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -453,3 +453,5 @@ } }, ] + +editObject = True \ No newline at end of file diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 5298b05c6..842ef2318 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -103,3 +103,16 @@ def get_virtual_guests(self, identifier, mask=None): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getVirtualGuestMembers/ """ return self.client.call('SoftLayer_Scale_Group', 'getVirtualGuestMembers', id=identifier, mask=mask, iter=True) + + def edit(self, identifier, template): + """Calls `SoftLayer_Scale_Group::editObject()`_ + + :param identifier: SoftLayer_Scale_Group id + :param template: `SoftLayer_Scale_Group`_ + .. _SoftLayer_Scale_Group::editObject(): + https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/editObject/ + .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ + """ + + + return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) \ No newline at end of file diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index de686a585..bc6887962 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,6 +7,9 @@ Tests for the autoscale cli command """ +import mock +import tempfile +from SoftLayer import fixtures from SoftLayer import testing @@ -49,6 +52,33 @@ def test_autoscale_tag(self): result = self.run_command(['autoscale', 'tag', '12345']) self.assert_no_fail(result) - def test_autoscale_edit(self): - result = self.run_command(['autoscale', 'edit', '12345']) + @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') + def test_autoscale_edit(self, manager): + result = self.run_command(['autoscale', 'edit', '12345', '--name', 'test']) self.assert_no_fail(result) + manager.assert_called_with('12345', {'name': 'test'}) + + @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') + def test_autoscale_edit_userdata(self, manager): + group = fixtures.SoftLayer_Scale_Group.getObject + template = { + 'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate'] + } + template['virtualGuestMemberTemplate']['userData'] = [{'value': 'test'}] + + result = self.run_command(['autoscale', 'edit', '12345', '--userdata', 'test']) + self.assert_no_fail(result) + manager.assert_called_with('12345', template) + + @mock.patch('SoftLayer.managers.autoscale.AutoScaleManager.edit') + def test_autoscale_edit_userfile(self, manager): + group = fixtures.SoftLayer_Scale_Group.getObject + template = { + 'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate'] + } + template['virtualGuestMemberTemplate']['userData'] = [{'value': ''}] + + with tempfile.NamedTemporaryFile() as userfile: + result = self.run_command(['autoscale', 'edit', '12345', '--userfile', userfile.name]) + self.assert_no_fail(result) + manager.assert_called_with('12345', template) diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 9826778b9..266200e40 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -114,3 +114,12 @@ def test_autoscale_get_virtual_guests_mask(self): identifier=11111, mask=test_mask ) + + def test_edit_object(self): + template = {'name': 'test'} + self.autoscale.edit(12345, template) + self.assert_called_with( + 'SoftLayer_Scale_Group', + 'editObject', + args=(template,), + identifier=12345) From f1922abed96cb4936dfaa285a18ed5b962ff0a34 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Tue, 8 Oct 2019 18:37:40 -0500 Subject: [PATCH 0186/1582] tox fixes --- SoftLayer/CLI/autoscale/edit.py | 6 +++--- SoftLayer/fixtures/SoftLayer_Scale_Group.py | 2 +- SoftLayer/managers/autoscale.py | 4 +--- tests/CLI/modules/autoscale_tests.py | 4 +++- tests/managers/autoscale_tests.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py index ef9b610d9..c470aebbf 100644 --- a/SoftLayer/CLI/autoscale/edit.py +++ b/SoftLayer/CLI/autoscale/edit.py @@ -30,10 +30,10 @@ def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory template['maximumMemberCount'] = maximum virt_template = {} if userdata: - virt_template['userData'] = [{"value":userdata}] + virt_template['userData'] = [{"value": userdata}] elif userfile: with open(userfile, 'r') as userfile_obj: - virt_template['userData'] = [{"value":userfile_obj.read()}] + virt_template['userData'] = [{"value": userfile_obj.read()}] virt_template['startCpus'] = cpu virt_template['maxMemory'] = memory @@ -48,7 +48,7 @@ def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory group['virtualGuestMemberTemplate'][key] = value clean_template['virtualGuestMemberTemplate'] = group['virtualGuestMemberTemplate'] - result = autoscale.edit(identifier, clean_template) + autoscale.edit(identifier, clean_template) click.echo("Done") diff --git a/SoftLayer/fixtures/SoftLayer_Scale_Group.py b/SoftLayer/fixtures/SoftLayer_Scale_Group.py index a6a666bde..f04d8f56e 100644 --- a/SoftLayer/fixtures/SoftLayer_Scale_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Scale_Group.py @@ -454,4 +454,4 @@ }, ] -editObject = True \ No newline at end of file +editObject = True diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 842ef2318..40d7ebe80 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -113,6 +113,4 @@ def edit(self, identifier, template): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/editObject/ .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ """ - - - return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) \ No newline at end of file + return self.client.call('SoftLayer_Scale_Group', 'editObject', template, id=identifier) diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index bc6887962..9ea7ebe46 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -8,10 +8,12 @@ Tests for the autoscale cli command """ import mock -import tempfile + from SoftLayer import fixtures from SoftLayer import testing +import tempfile + class AutoscaleTests(testing.TestCase): diff --git a/tests/managers/autoscale_tests.py b/tests/managers/autoscale_tests.py index 266200e40..6da505409 100644 --- a/tests/managers/autoscale_tests.py +++ b/tests/managers/autoscale_tests.py @@ -116,7 +116,7 @@ def test_autoscale_get_virtual_guests_mask(self): ) def test_edit_object(self): - template = {'name': 'test'} + template = {'name': 'test'} self.autoscale.edit(12345, template) self.assert_called_with( 'SoftLayer_Scale_Group', From daeaff42a1af1212c30f42da26c2553898481fa3 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Tue, 8 Oct 2019 18:44:58 -0500 Subject: [PATCH 0187/1582] removed debug code --- SoftLayer/CLI/autoscale/tag.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/autoscale/tag.py b/SoftLayer/CLI/autoscale/tag.py index e1fe1745b..58e4101b7 100644 --- a/SoftLayer/CLI/autoscale/tag.py +++ b/SoftLayer/CLI/autoscale/tag.py @@ -31,4 +31,3 @@ def cli(env, identifier, tags): click.echo("Setting tags for {}".format(real_guest.get('hostname'))) vsmanager.set_tags(tags, real_guest.get('id'),) click.echo("Done") - # pp(guests) From 37786dc33a2c15fbe2188baa29608043431233f0 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 10 Oct 2019 14:36:00 -0500 Subject: [PATCH 0188/1582] #962 fixed image list in rest --- SoftLayer/CLI/image/list.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/image/list.py b/SoftLayer/CLI/image/list.py index fd45932c4..65ff53eb6 100644 --- a/SoftLayer/CLI/image/list.py +++ b/SoftLayer/CLI/image/list.py @@ -12,9 +12,7 @@ @click.command() @click.option('--name', default=None, help='Filter on image name') -@click.option('--public/--private', - is_flag=True, - default=None, +@click.option('--public/--private', is_flag=True, default=None, help='Display only public or private images') @environment.pass_env def cli(env, name, public): @@ -24,30 +22,22 @@ def cli(env, name, public): images = [] if public in [False, None]: - for image in image_mgr.list_private_images(name=name, - mask=image_mod.MASK): + for image in image_mgr.list_private_images(name=name, mask=image_mod.MASK): images.append(image) if public in [True, None]: - for image in image_mgr.list_public_images(name=name, - mask=image_mod.MASK): + for image in image_mgr.list_public_images(name=name, mask=image_mod.MASK): images.append(image) - table = formatting.Table(['id', - 'name', - 'type', - 'visibility', - 'account']) + table = formatting.Table(['id', 'name', 'type', 'visibility', 'account']) - images = [image for image in images if image['parentId'] == ''] + images = [image for image in images if not image['parentId']] for image in images: - visibility = (image_mod.PUBLIC_TYPE if image['publicFlag'] - else image_mod.PRIVATE_TYPE) + visibility = (image_mod.PUBLIC_TYPE if image['publicFlag'] else image_mod.PRIVATE_TYPE) table.add_row([ image.get('id', formatting.blank()), - formatting.FormattedItem(image['name'], - click.wrap_text(image['name'], width=50)), + formatting.FormattedItem(image['name'], click.wrap_text(image['name'], width=50)), formatting.FormattedItem( utils.lookup(image, 'imageType', 'keyName'), utils.lookup(image, 'imageType', 'name')), From 4543683f085d78466be74d1ad41c07f50be4197b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 10 Oct 2019 18:11:35 -0400 Subject: [PATCH 0189/1582] adding fixture not implemented message --- SoftLayer/testing/__init__.py | 2 +- SoftLayer/testing/xmlrpc.py | 9 ++++++++ tests/CLI/modules/call_api_tests.py | 35 +++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index a2caa8888..d02e60f0e 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -147,7 +147,7 @@ def assert_called_with(self, service, method, **props): def assert_no_fail(self, result): """Fail when a failing click result has an error""" if result.exception: - print(result.output) + print(result.exception) raise result.exception self.assertEqual(result.exit_code, 0) diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py index a38e85eeb..9f5588c27 100644 --- a/SoftLayer/testing/xmlrpc.py +++ b/SoftLayer/testing/xmlrpc.py @@ -66,6 +66,15 @@ def do_POST(self): except UnicodeDecodeError: self.wfile.write(response_body) + except NotImplementedError as ex: + self.send_response(200) + self.end_headers() + response = xmlrpc.client.Fault(404, str(ex)) + response_body = xmlrpc.client.dumps(response, + allow_none=True, + methodresponse=True) + self.wfile.write(response_body.encode('utf-8')) + except SoftLayer.SoftLayerAPIError as ex: self.send_response(200) self.end_headers() diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index 123528607..d99c59d35 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -8,6 +8,7 @@ from SoftLayer.CLI import call_api from SoftLayer.CLI import exceptions +from SoftLayer import SoftLayerAPIError from SoftLayer import testing import pytest @@ -229,3 +230,37 @@ def test_parameters(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Service', 'method', args=('arg1', '1234')) + + def test_fixture_not_implemented(self): + service = 'SoftLayer_Test' + method = 'getTest' + result = self.run_command(['call-api', service, method]) + self.assertEqual(result.exit_code, 1) + self.assert_called_with(service, method) + self.assertIsInstance(result.exception, SoftLayerAPIError) + output = '{} fixture is not implemented'.format(service) + self.assertIn(output, result.exception.faultString) + + def test_fixture_not_implemented_method(self): + call_service = 'SoftLayer_Account' + call_method = 'getTest' + result = self.run_command(['call-api', call_service, call_method]) + self.assertEqual(result.exit_code, 1) + self.assert_called_with(call_service, call_method) + self.assertIsInstance(result.exception, SoftLayerAPIError) + output = '%s::%s fixture is not implemented' % (call_service, call_method) + self.assertIn(output, result.exception.faultString) + + def test_fixture_exception(self): + call_service = 'SoftLayer_Account' + call_method = 'getTest' + result = self.run_command(['call-api', call_service, call_method]) + try: + self.assert_no_fail(result) + except Exception as ex: + print(ex) + self.assertEqual(result.exit_code, 1) + self.assert_called_with(call_service, call_method) + self.assertIsInstance(result.exception, SoftLayerAPIError) + output = '%s::%s fixture is not implemented' % (call_service, call_method) + self.assertIn(output, result.exception.faultString) From 17b94cf1cee0326439d2b00fecdfe764c3affff7 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 11 Oct 2019 14:41:20 -0500 Subject: [PATCH 0190/1582] v5.8.1 --- CHANGELOG.md | 13 +++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- snap/snapcraft.yaml | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a28f161d6..30e337124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log +## [5.8.1] - 2019-10-11 +- https://github.com/softlayer/softlayer-python/compare/v5.8.0...v5.8.1 + ++ #1169 Drop python 2.7 support ++ #1170 Added CS# to ticket listing ++ #1162 Fixed issue looking up OS keyName instead of referenceCode ++ #627 Autoscale support + * slcli autoscale detail + * slcli autoscale edit + * slcli autoscale list + * slcli autoscale logs + * slcli autoscale scale + * slcli autoscale tag ## [5.8.0] - 2019-09-04 - https://github.com/softlayer/softlayer-python/compare/v5.7.2...v5.8.0 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 1171bd578..454806696 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.0' +VERSION = 'v5.8.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 ae0e85e41..a7bd56770 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.0', + version='5.8.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a2190f100..b08438c82 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.8.0+git' # check versioning +version: '5.8.1+git' # check versioning summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 479035fd35c9217b6cd3dba1182ddfcb5c1df630 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 15 Oct 2019 12:34:56 -0400 Subject: [PATCH 0191/1582] catching for the NameError exception --- SoftLayer/testing/xmlrpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py index 9f5588c27..1e3bf5f1d 100644 --- a/SoftLayer/testing/xmlrpc.py +++ b/SoftLayer/testing/xmlrpc.py @@ -66,7 +66,7 @@ def do_POST(self): except UnicodeDecodeError: self.wfile.write(response_body) - except NotImplementedError as ex: + except (NotImplementedError, NameError) as ex: self.send_response(200) self.end_headers() response = xmlrpc.client.Fault(404, str(ex)) From 72908a32c46360c68f8efbd969650c689c52eef1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Oct 2019 17:28:34 -0500 Subject: [PATCH 0192/1582] Update README.rst Adds mention of dropping python 2.7 support to the readme. --- README.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.rst b/README.rst index c543ce5ae..a742ef03d 100644 --- a/README.rst +++ b/README.rst @@ -129,6 +129,13 @@ System Requirements * A connection to SoftLayer's private network is required to use our private network API endpoints. +Pyhton 2.7 Support +------------------ +As of version 5.8.0 SoftLayer-Python will no longer support python2.7, which is _`End Of Life as of 2020 `_. +If you cannot install python 3.6+ for some reason, you will need to use a version of softlayer-python <= 5.7.2 + + + Python Packages --------------- * ptable >= 0.9.2 From 6b011b546b1a2ba2cc08857c0ea16d91b68638e0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Oct 2019 17:30:22 -0500 Subject: [PATCH 0193/1582] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index a742ef03d..5cda832cf 100644 --- a/README.rst +++ b/README.rst @@ -131,7 +131,7 @@ System Requirements Pyhton 2.7 Support ------------------ -As of version 5.8.0 SoftLayer-Python will no longer support python2.7, which is _`End Of Life as of 2020 `_. +As of version 5.8.0 SoftLayer-Python will no longer support python2.7, which is `End Of Life as of 2020 `_ . If you cannot install python 3.6+ for some reason, you will need to use a version of softlayer-python <= 5.7.2 From 80a965de712f36f598bf1092f0c0a9e5837f7121 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 4 Nov 2019 15:58:37 -0600 Subject: [PATCH 0194/1582] #1181 fixed issue where vs reboot was using the hardware manager, and not the VS manager to resolve ids --- SoftLayer/CLI/virt/power.py | 2 +- tests/CLI/modules/autoscale_tests.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/power.py b/SoftLayer/CLI/virt/power.py index f3c04a9b7..20f73d04f 100644 --- a/SoftLayer/CLI/virt/power.py +++ b/SoftLayer/CLI/virt/power.py @@ -35,7 +35,7 @@ def reboot(env, identifier, hard): """Reboot an active virtual server.""" virtual_guest = env.client['Virtual_Guest'] - mgr = SoftLayer.HardwareManager(env.client) + mgr = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'VS') if not (env.skip_confirmations or formatting.confirm('This will reboot the VS with id %s. ' diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 9ea7ebe46..5d178c5e7 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -74,6 +74,9 @@ 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")): + self.skipTest("Test doesn't work in Windows") group = fixtures.SoftLayer_Scale_Group.getObject template = { 'virtualGuestMemberTemplate': group['virtualGuestMemberTemplate'] From f7c508021d10518b9167a302c705c9f09bc077c8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 4 Nov 2019 16:12:40 -0600 Subject: [PATCH 0195/1582] tox fixes --- SoftLayer/transports.py | 2 +- tests/CLI/modules/autoscale_tests.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 8849a5c2f..297249852 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -9,6 +9,7 @@ import json import logging import re +from string import Template import time import xmlrpc.client @@ -256,7 +257,6 @@ def print_reproduceable(self, request): :param request request: Request object """ - from string import Template output = Template('''============= testing.py ============= import requests from requests.auth import HTTPBasicAuth diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 5d178c5e7..6d0e543da 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -8,6 +8,7 @@ Tests for the autoscale cli command """ import mock +import sys from SoftLayer import fixtures from SoftLayer import testing From 324ee268caf5161b3f1a689686b3712e892d1103 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 13 Nov 2019 16:17:22 -0600 Subject: [PATCH 0196/1582] Added ability to fallback to json output in case of unicdoe errors #771 --- SoftLayer/CLI/environment.py | 12 +++++++++--- tests/CLI/environment_tests.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index cb914fef3..c73bc6385 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -46,14 +46,20 @@ def err(self, output, newline=True): """Outputs an error string to the console (stderr).""" click.echo(output, nl=newline, err=True) - def fmt(self, output): + def fmt(self, output, fmt=None): """Format output based on current the environment format.""" - return formatting.format_output(output, fmt=self.format) + if fmt is None: + fmt = self.format + return formatting.format_output(output, fmt) def fout(self, output, newline=True): """Format the input and output to the console (stdout).""" if output is not None: - self.out(self.fmt(output), newline=newline) + try: + self.out(self.fmt(output), newline=newline) + except UnicodeEncodeError: + # If we hit an undecodeable entry, just try outputting as json. + self.out(self.fmt(output, 'json'), newline=newline) def input(self, prompt, default=None, show_default=True): """Provide a command prompt.""" diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index 5d04e153d..fa90ba1db 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -62,3 +62,14 @@ def test_resolve_alias(self): r = self.env.resolve_alias('realname') self.assertEqual(r, 'realname') + + @mock.patch('click.echo') + def test_print_unicode(self, echo): + output = "\u3010TEST\u3011 image" + # https://docs.python.org/3.6/library/exceptions.html#UnicodeError + echo.side_effect = [ + UnicodeEncodeError('utf8', output, 0, 1, "Test Exception"), + output + ] + self.env.fout(output) + self.assertEqual(2, echo.call_count) From 156ae3e3956dde65cf5bbfaca598a2106648afc8 Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 14 Nov 2019 13:38:40 -0600 Subject: [PATCH 0197/1582] Fix python misspelling --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5cda832cf..cdc376753 100644 --- a/README.rst +++ b/README.rst @@ -129,7 +129,7 @@ System Requirements * A connection to SoftLayer's private network is required to use our private network API endpoints. -Pyhton 2.7 Support +Python 2.7 Support ------------------ As of version 5.8.0 SoftLayer-Python will no longer support python2.7, which is `End Of Life as of 2020 `_ . If you cannot install python 3.6+ for some reason, you will need to use a version of softlayer-python <= 5.7.2 @@ -147,6 +147,6 @@ Python Packages Copyright --------- -This software is Copyright (c) 2016-2018 SoftLayer Technologies, Inc. +This software is Copyright (c) 2016-2019 SoftLayer Technologies, Inc. See the bundled LICENSE file for more information. From 7d2b3a0627d12af6d90546a85a5781782a4acd27 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 15 Nov 2019 14:23:34 -0400 Subject: [PATCH 0198/1582] Fix cdn detail usage. --- SoftLayer/managers/cdn.py | 14 +++++++++++--- tests/managers/cdn_tests.py | 10 ++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 95a32f870..e4c827de1 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -21,6 +21,8 @@ class CDNManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client + self._start_date = None + self._end_date = None self.cdn_configuration = self.client['Network_CdnMarketplace_Configuration_Mapping'] self.cdn_path = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path'] self.cdn_metrics = self.client['Network_CdnMarketplace_Metrics'] @@ -151,10 +153,16 @@ def get_usage_metrics(self, unique_id, history=30, frequency="aggregate"): _start = utils.days_to_datetime(history) _end = utils.days_to_datetime(0) - _start_date = utils.timestamp(_start) - _end_date = utils.timestamp(_end) + self._start_date = utils.timestamp(_start) + self._end_date = utils.timestamp(_end) - usage = self.cdn_metrics.getMappingUsageMetrics(unique_id, _start_date, _end_date, frequency) + usage = self.cdn_metrics.getMappingUsageMetrics(unique_id, self._start_date, self._end_date, frequency) # The method getMappingUsageMetrics() returns an array but there is only 1 object return usage[0] + + def get_start_data(self): + return self._start_date + + def get_end_date(self): + return self._end_date diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 41a675c6d..f063c95fc 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -31,15 +31,9 @@ def test_detail_cdn(self): def test_detail_usage_metric(self): self.cdn_client.get_usage_metrics(12345, history=30, frequency="aggregate") - _start = utils.days_to_datetime(30) - _end = utils.days_to_datetime(0) - - _start_date = utils.timestamp(_start) - _end_date = utils.timestamp(_end) - args = (12345, - _start_date, - _end_date, + self.cdn_client.get_start_data(), + self.cdn_client.get_end_date(), "aggregate") self.assert_called_with('SoftLayer_Network_CdnMarketplace_Metrics', 'getMappingUsageMetrics', From 8099efc9fef9481c0bd767e2607d0bf6a6d667bb Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 15 Nov 2019 14:45:46 -0400 Subject: [PATCH 0199/1582] Fix analysis tox. --- SoftLayer/managers/cdn.py | 6 ++++-- tests/managers/cdn_tests.py | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index e4c827de1..2614d2676 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -161,8 +161,10 @@ def get_usage_metrics(self, unique_id, history=30, frequency="aggregate"): # The method getMappingUsageMetrics() returns an array but there is only 1 object return usage[0] - def get_start_data(self): + @property + def start_data(self): return self._start_date - def get_end_date(self): + @property + def end_date(self): return self._end_date diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index f063c95fc..31c3c4fa0 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -7,7 +7,6 @@ from SoftLayer.managers import cdn from SoftLayer import testing -from SoftLayer import utils class CDNTests(testing.TestCase): @@ -32,8 +31,8 @@ def test_detail_usage_metric(self): self.cdn_client.get_usage_metrics(12345, history=30, frequency="aggregate") args = (12345, - self.cdn_client.get_start_data(), - self.cdn_client.get_end_date(), + self.cdn_client.start_data, + self.cdn_client.end_date, "aggregate") self.assert_called_with('SoftLayer_Network_CdnMarketplace_Metrics', 'getMappingUsageMetrics', From 141cd2357136d0864e6c71ee355e4a0a9d33c7e9 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 15 Nov 2019 14:55:30 -0400 Subject: [PATCH 0200/1582] Fix analysis tox. --- SoftLayer/managers/cdn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 2614d2676..2ead8c1fc 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -163,8 +163,10 @@ def get_usage_metrics(self, unique_id, history=30, frequency="aggregate"): @property def start_data(self): + """Retrieve the cdn usage metric start date.""" return self._start_date @property def end_date(self): + """Retrieve the cdn usage metric end date.""" return self._end_date From b2d4f07adfabda5f3b5338b82184b1f6c5cceedf Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 15 Nov 2019 17:06:07 -0400 Subject: [PATCH 0201/1582] adding subnet to order template if was requested --- SoftLayer/managers/vs.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 9f2b6c94c..24a264b7f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -429,7 +429,6 @@ def _generate_create_dict( def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None): - parameters = {} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} @@ -532,7 +531,16 @@ def verify_create_instance(self, **kwargs): """ kwargs.pop('tags', None) create_options = self._generate_create_dict(**kwargs) - return self.guest.generateOrderTemplate(create_options) + template = self.guest.generateOrderTemplate(create_options) + if 'private_subnet' in kwargs or 'public_subnet' in kwargs: + vsi = template['virtualGuests'][0] + network_components = self._create_network_components(kwargs.get('public_vlan', None), + kwargs.get('private_vlan', None), + kwargs.get('private_subnet', None), + kwargs.get('public_subnet', None)) + vsi.update(network_components) + + return template def create_instance(self, **kwargs): """Creates a new virtual server instance. From 60485f1d8f7c6d9c5f739b4ed09ac78a9b8ba590 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 15 Nov 2019 16:01:55 -0600 Subject: [PATCH 0202/1582] Version to 5.8.2 --- CHANGELOG.md | 10 ++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30e337124..30f37db80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ # Change Log +## [5.8.2] - 2019-11-15 +- https://github.com/softlayer/softlayer-python/compare/v5.8.1...v5.8.2 + + ++ #1186 Fixed a unit test that could fail if the test took too long to run. ++ #1183 Added a check to ensure subnet and vlan options are properly added to the order for virtual servers. ++ #1184 Fixed a readme misspelling. ++ #1182 Fixed vs reboot unable to resolve vs names. ++ #1095 Handle missing Fixtures better for unit tests. + ## [5.8.1] - 2019-10-11 - https://github.com/softlayer/softlayer-python/compare/v5.8.0...v5.8.1 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 454806696..f3555da38 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.1' +VERSION = 'v5.8.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 a7bd56770..9886e03a2 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.1', + version='5.8.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 4e1d727f96815adc20ddd3027d3e6d2e1aac1702 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 18 Nov 2019 12:35:09 -0600 Subject: [PATCH 0203/1582] #771 fixing up unicodeError handling on windows --- SoftLayer/CLI/environment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c73bc6385..e51b88e78 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -59,6 +59,7 @@ def fout(self, output, newline=True): self.out(self.fmt(output), newline=newline) except UnicodeEncodeError: # If we hit an undecodeable entry, just try outputting as json. + self.out("UnicodeEncodeError detected, printing as JSON.") self.out(self.fmt(output, 'json'), newline=newline) def input(self, prompt, default=None, show_default=True): From 1a59b8a65de8e6543858e09011c056b672e16ae1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 18 Nov 2019 14:12:45 -0600 Subject: [PATCH 0204/1582] removed exception message in environment.fout when a UnicodeEncodeError was encountered --- SoftLayer/CLI/environment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index e51b88e78..c73bc6385 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -59,7 +59,6 @@ def fout(self, output, newline=True): self.out(self.fmt(output), newline=newline) except UnicodeEncodeError: # If we hit an undecodeable entry, just try outputting as json. - self.out("UnicodeEncodeError detected, printing as JSON.") self.out(self.fmt(output, 'json'), newline=newline) def input(self, prompt, default=None, show_default=True): From 1cf06aa8ef627a06e2b95cf5804192d28a12612d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 3 Dec 2019 17:30:45 -0400 Subject: [PATCH 0205/1582] Fix order vs dedicated. --- SoftLayer/managers/ordering.py | 26 +++++++++++++++-- tests/managers/ordering_tests.py | 50 ++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e20378914..de9f2d874 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -351,8 +351,9 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): keynames in the given package """ - mask = 'id, itemCategory, keyName, prices[categories]' + mask = 'id, capacity, itemCategory, keyName, prices[categories]' items = self.list_items(package_keyname, mask=mask) + item_capacity = self.get_item_capacity(items, item_keynames) prices = [] category_dict = {"gpu0": -1, "pcie_slot0": -1} @@ -374,7 +375,10 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): # in which the order is made item_category = matching_item['itemCategory']['categoryCode'] if item_category not in category_dict: - price_id = self.get_item_price_id(core, matching_item['prices']) + if core is None: + price_id = self.get_item_price_id(item_capacity, matching_item['prices']) + else: + price_id = self.get_item_price_id(core, matching_item['prices']) else: # GPU and PCIe items has two generic prices and they are added to the list # according to the number of items in the order. @@ -404,6 +408,24 @@ def get_item_price_id(core, prices): price_id = price['id'] return price_id + def get_item_capacity(self, items, item_keynames): + """Get item capacity. + This function is used to get the item capacity data + + :param items: items data. + :param item_keynames list. + :returns: An item id. + + """ + item_capacity = None + for item_keyname in item_keynames: + for item in items: + if item['keyName'] == item_keyname: + if "GUEST_CORE" in item["keyName"]: + item_capacity = item['capacity'] + break + return item_capacity + def get_preset_prices(self, preset): """Get preset item prices. diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 40f9f5db3..a76289ca6 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -309,7 +309,26 @@ def test_get_price_id_list(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') + self.assertEqual([price1['id'], price2['id']], prices) + + def test_get_price_id_list_no_core(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': None, 'categories': [{"categoryCode": "guest_core"}], + 'itemCategory': [category1]} + item1 = {'id': 1111, 'keyName': 'ITEM1', 'itemCategory': category1, 'prices': [price1]} + category2 = {'categoryCode': 'cat2'} + price2 = {'id': 5678, 'locationGroupId': None, 'categories': [category2]} + item2 = {'id': 2222, 'keyName': 'ITEM2', 'itemCategory': category2, 'prices': [price2]} + + with mock.patch.object(self.ordering, 'list_items') as list_mock: + list_mock.return_value = [item1, item2] + + prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], None) + + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) def test_get_price_id_list_item_not_found(self): @@ -323,7 +342,8 @@ def test_get_price_id_list_item_not_found(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.get_price_id_list, 'PACKAGE_KEYNAME', ['ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME", str(exc)) def test_get_price_id_list_gpu_items_with_two_categories(self): @@ -337,7 +357,8 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) def test_generate_no_complex_type(self): @@ -587,7 +608,8 @@ def test_location_group_id_none(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) def test_location_groud_id_empty(self): @@ -604,7 +626,8 @@ def test_location_groud_id_empty(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, itemCategory, keyName, prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) def test_get_item_price_id_without_capacity_restriction(self): @@ -672,3 +695,20 @@ def test_clean_quote_verify(self): order_container = call.args[0] self.assertNotIn('testProperty', order_container) self.assertNotIn('reservedCapacityId', order_container) + + def test_get_item_capacity(self): + + items = [{ + "capacity": "1", + "id": 6131, + "keyName": "OS_RHEL_7_X_LAMP_64_BIT", + }, + { + "capacity": "1", + "id": 10201, + "keyName": "GUEST_CORE_1_DEDICATED", + }] + + item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) + + self.assertEqual(1, int(item_capacity)) From 491734a5b528f04a4c522e720637bb15ec9cdb68 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 3 Dec 2019 17:51:39 -0400 Subject: [PATCH 0206/1582] Refactor docstring summary for get item capacity method. --- SoftLayer/managers/ordering.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index de9f2d874..0cd015e77 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -409,14 +409,7 @@ def get_item_price_id(core, prices): return price_id def get_item_capacity(self, items, item_keynames): - """Get item capacity. - This function is used to get the item capacity data - - :param items: items data. - :param item_keynames list. - :returns: An item id. - - """ + """Get item capacity.""" item_capacity = None for item_keyname in item_keynames: for item in items: From 3e0faade466cdbc2b13b45f5bebc3bcb38dbdd75 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 5 Dec 2019 19:00:56 -0400 Subject: [PATCH 0207/1582] Fix hardware detail bandwidth allocation. --- SoftLayer/CLI/hardware/detail.py | 11 +++++++---- SoftLayer/managers/hardware.py | 7 +++++-- tests/managers/hardware_tests.py | 10 +++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 3a55f1b6d..73fa481fe 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -92,16 +92,19 @@ def cli(env, identifier, passwords, price): def _bw_table(bw_data): - """Generates a bandwidth useage table""" + """Generates a bandwidth usage table""" table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) - for bw_point in bw_data.get('useage'): + for bw_point in bw_data.get('usage'): bw_type = 'Private' allotment = 'N/A' if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': bw_type = 'Public' - allotment = utils.lookup(bw_data, 'allotment', 'amount') - if allotment is None: + if bw_data.get('allotment') is None: allotment = '-' + else: + allotment = utils.lookup(bw_data, 'allotment', 'amount') + if allotment is None: + allotment = '-' table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d442d42e0..8fe870d46 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -697,8 +697,11 @@ def get_bandwidth_allocation(self, instance_id): a_mask = "mask[allocation[amount]]" allotment = self.client.call('Hardware_Server', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) u_mask = "mask[amountIn,amountOut,type]" - useage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) - return {'allotment': allotment.get('allocation'), 'useage': useage} + usage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) + allotment_bandwidth = None + if allotment is not "": + allotment_bandwidth = allotment.get('allocation') + return {'allotment': allotment_bandwidth, 'usage': usage} def _get_extra_price_id(items, key_name, hourly, location): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index cf10224a4..201206eca 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -446,7 +446,7 @@ def test_get_bandwidth_allocation(self): self.assert_called_with('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail', identifier=1234) self.assert_called_with('SoftLayer_Hardware_Server', 'getBillingCycleBandwidthUsage', identifier=1234) self.assertEqual(result['allotment']['amount'], '250') - self.assertEqual(result['useage'][0]['amountIn'], '.448') + self.assertEqual(result['usage'][0]['amountIn'], '.448') def test_get_bandwidth_allocation_no_allotment(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') @@ -456,6 +456,14 @@ def test_get_bandwidth_allocation_no_allotment(self): self.assertEqual(None, result['allotment']) + def test_get_bandwidth_allocation_empty_allotment(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') + mock.return_value = "" + + result = self.hardware.get_bandwidth_allocation(1234) + + self.assertEqual(None, result['allotment']) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): From eba33fd56bc3aca3cc282a586e02fbac09d7143b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 6 Dec 2019 13:04:26 -0400 Subject: [PATCH 0208/1582] Refactor hardware detail bandwidth allocation and add more unit test. --- SoftLayer/CLI/hardware/detail.py | 4 +--- SoftLayer/managers/hardware.py | 7 +++---- tests/CLI/modules/server_tests.py | 11 +++++++++++ tests/managers/hardware_tests.py | 19 +++++++++++-------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 73fa481fe..89f0cb0ee 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -99,12 +99,10 @@ def _bw_table(bw_data): allotment = 'N/A' if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': bw_type = 'Public' - if bw_data.get('allotment') is None: + if not bw_data.get('allotment'): allotment = '-' else: allotment = utils.lookup(bw_data, 'allotment', 'amount') - if allotment is None: - allotment = '-' table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8fe870d46..fa4ee42a8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -698,10 +698,9 @@ def get_bandwidth_allocation(self, instance_id): allotment = self.client.call('Hardware_Server', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) u_mask = "mask[amountIn,amountOut,type]" usage = self.client.call('Hardware_Server', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) - allotment_bandwidth = None - if allotment is not "": - allotment_bandwidth = allotment.get('allocation') - return {'allotment': allotment_bandwidth, 'usage': usage} + if allotment: + return {'allotment': allotment.get('allocation'), 'usage': usage} + return {'allotment': allotment, 'usage': usage} def _get_extra_price_id(items, key_name, hourly, location): diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 70d4c3167..29ec65d40 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -127,6 +127,17 @@ def test_detail_vs_empty_tag(self): ['example-tag'], ) + def test_detail_empty_allotment(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') + mock.return_value = None + result = self.run_command(['server', 'detail', '100']) + + self.assert_no_fail(result) + self.assertEqual( + json.loads(result.output)['Bandwidth'][0]['Allotment'], + '-', + ) + def test_list_servers(self): result = self.run_command(['server', 'list', '--tag=openstack']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 201206eca..79d9c19df 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -8,14 +8,11 @@ import mock - import SoftLayer - from SoftLayer import fixtures from SoftLayer import managers from SoftLayer import testing - MINIMAL_TEST_CREATE_ARGS = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', @@ -448,17 +445,23 @@ def test_get_bandwidth_allocation(self): self.assertEqual(result['allotment']['amount'], '250') self.assertEqual(result['usage'][0]['amountIn'], '.448') - def test_get_bandwidth_allocation_no_allotment(self): + def test_get_bandwidth_allocation_with_allotment(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') - mock.return_value = {} + mock.return_value = { + "allocationId": 11111, + "id": 22222, + "allocation": { + "amount": "2000" + } + } result = self.hardware.get_bandwidth_allocation(1234) - self.assertEqual(None, result['allotment']) + self.assertEqual(2000, int(result['allotment']['amount'])) - def test_get_bandwidth_allocation_empty_allotment(self): + def test_get_bandwidth_allocation_no_allotment(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getBandwidthAllotmentDetail') - mock.return_value = "" + mock.return_value = None result = self.hardware.get_bandwidth_allocation(1234) From c5d413de7576832890b66c141afe08e005fb7879 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 6 Dec 2019 13:06:09 -0400 Subject: [PATCH 0209/1582] fix hardware unit test import. --- tests/managers/hardware_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 79d9c19df..fbbdb2218 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -9,6 +9,7 @@ import mock import SoftLayer + from SoftLayer import fixtures from SoftLayer import managers from SoftLayer import testing From 6025c3ef34f97a5412e84faf964667e34af0899d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 11 Dec 2019 15:40:25 -0400 Subject: [PATCH 0210/1582] Fix quote file storage. --- SoftLayer/managers/ordering.py | 7 ++++++- tests/managers/ordering_tests.py | 21 ++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 0cd015e77..cc9a6344b 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -405,7 +405,9 @@ def get_item_price_id(core, prices): price_id = price['id'] # this check is mostly to work nicely with preset configs elif capacity_min <= int(core) <= capacity_max: - price_id = price['id'] + if "STORAGE" in price.get("capacityRestrictionType") or "CORE" in price.get( + "capacityRestrictionType"): + price_id = price['id'] return price_id def get_item_capacity(self, items, item_keynames): @@ -417,6 +419,9 @@ def get_item_capacity(self, items, item_keynames): if "GUEST_CORE" in item["keyName"]: item_capacity = item['capacity'] break + if "TIER" in item["keyName"]: + item_capacity = item['capacity'] + break return item_capacity def get_preset_prices(self, preset): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index a76289ca6..7c5522ca6 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -640,12 +640,27 @@ def test_get_item_price_id_without_capacity_restriction(self): self.assertEqual(1234, price_id) - def test_get_item_price_id_with_capacity_restriction(self): + def test_get_item_price_id_core_with_capacity_restriction(self): category1 = {'categoryCode': 'cat1'} price1 = [{'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", 'categories': [category1]}, + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "CORE", + 'categories': [category1]}, {'id': 2222, 'locationGroupId': '', "capacityRestrictionMaximum": "56", - "capacityRestrictionMinimum": "36", 'categories': [category1]}] + "capacityRestrictionMinimum": "36", "capacityRestrictionType": "CORE", + 'categories': [category1]}] + + price_id = self.ordering.get_item_price_id("8", price1) + + self.assertEqual(1234, price_id) + + def test_get_item_price_id_storage_with_capacity_restriction(self): + category1 = {'categoryCode': 'cat1'} + price1 = [{'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1]}, + {'id': 2222, 'locationGroupId': '', "capacityRestrictionMaximum": "56", + "capacityRestrictionMinimum": "36", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1]}] price_id = self.ordering.get_item_price_id("8", price1) From 2274cf0e4a9a791e9e3e959a6d483afdc1c6acff Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 11 Dec 2019 16:00:50 -0400 Subject: [PATCH 0211/1582] Add unit test for ordering manager. --- tests/managers/ordering_tests.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 7c5522ca6..35d5b819a 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -711,7 +711,7 @@ def test_clean_quote_verify(self): self.assertNotIn('testProperty', order_container) self.assertNotIn('reservedCapacityId', order_container) - def test_get_item_capacity(self): + def test_get_item_capacity_core(self): items = [{ "capacity": "1", @@ -727,3 +727,20 @@ def test_get_item_capacity(self): item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) self.assertEqual(1, int(item_capacity)) + + def test_get_item_capacity_storage(self): + + items = [{ + "capacity": "1", + "id": 6131, + "keyName": "STORAGE_SPACE_FOR_2_IOPS_PER_GB", + }, + { + "capacity": "1", + "id": 10201, + "keyName": "READHEAVY_TIER", + }] + + item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) + + self.assertEqual(1, int(item_capacity)) From 054656a8bc6b58831a4e25e98cec913463fff384 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 11 Dec 2019 17:44:11 -0600 Subject: [PATCH 0212/1582] 5.8.3 changelog --- CHANGELOG.md | 9 +++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f37db80..389018f52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Change Log +## [5.8.3] - 2019-12-11 +https://github.com/softlayer/softlayer-python/compare/v5.8.2...v5.8.3 + +- #771 Fixed unicode errors in image list (for windows) +- #1191 Fixed ordering virtual server dedicated from the CLI +- #1155 Fixed capacity restriction when ordering storage quotes +- #1192 Fixed hardware detail bandwidth allocation errors. + + ## [5.8.2] - 2019-11-15 - https://github.com/softlayer/softlayer-python/compare/v5.8.1...v5.8.2 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index f3555da38..b25cff026 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.2' +VERSION = 'v5.8.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 9886e03a2..814d43269 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.2', + version='5.8.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 2b9215cc0ec3918e51644b248b821e931a835450 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 17 Dec 2019 17:55:02 -0400 Subject: [PATCH 0213/1582] Fix block storage failback and failover. --- SoftLayer/CLI/block/replication/failback.py | 8 ++------ SoftLayer/CLI/block/replication/failover.py | 9 ++------- SoftLayer/managers/block.py | 11 ++++------- tests/CLI/modules/block_tests.py | 8 +++----- tests/managers/block_tests.py | 7 +++---- 5 files changed, 14 insertions(+), 29 deletions(-) diff --git a/SoftLayer/CLI/block/replication/failback.py b/SoftLayer/CLI/block/replication/failback.py index 3887c29c9..88ab6d627 100644 --- a/SoftLayer/CLI/block/replication/failback.py +++ b/SoftLayer/CLI/block/replication/failback.py @@ -8,16 +8,12 @@ @click.command() @click.argument('volume-id') -@click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env -def cli(env, volume_id, replicant_id): +def cli(env, volume_id): """Failback a block volume from the given replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - success = block_storage_manager.failback_from_replicant( - volume_id, - replicant_id - ) + success = block_storage_manager.failback_from_replicant(volume_id) if success: click.echo("Failback from replicant is now in progress.") diff --git a/SoftLayer/CLI/block/replication/failover.py b/SoftLayer/CLI/block/replication/failover.py index 545175c4a..cd36b2271 100644 --- a/SoftLayer/CLI/block/replication/failover.py +++ b/SoftLayer/CLI/block/replication/failover.py @@ -9,19 +9,14 @@ @click.command() @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") -@click.option('--immediate', - is_flag=True, - default=False, - help="Failover to replicant immediately.") @environment.pass_env -def cli(env, volume_id, replicant_id, immediate): +def cli(env, volume_id, replicant_id): """Failover a block volume to the given replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) success = block_storage_manager.failover_to_replicant( volume_id, - replicant_id, - immediate + replicant_id ) if success: diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 42cc97e71..1f8c6ec5c 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -525,28 +525,25 @@ def cancel_block_volume(self, volume_id, reason, id=billing_item_id) - def failover_to_replicant(self, volume_id, replicant_id, immediate=False): + def failover_to_replicant(self, volume_id, replicant_id): """Failover to a volume replicant. :param integer volume_id: The id of the volume :param integer replicant_id: ID of replicant to failover to - :param boolean immediate: Flag indicating if failover is immediate :return: Returns whether failover was successful or not """ return self.client.call('Network_Storage', 'failoverToReplicant', - replicant_id, immediate, id=volume_id) + replicant_id, id=volume_id) - def failback_from_replicant(self, volume_id, replicant_id): + def failback_from_replicant(self, volume_id): """Failback from a volume replicant. :param integer volume_id: The id of the volume - :param integer replicant_id: ID of replicant to failback from :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', - replicant_id, id=volume_id) + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def set_credential_password(self, access_id, password): """Sets the password for an access host diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 225476976..b8629de8a 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -440,7 +440,7 @@ def test_deauthorize_host_to_volume(self): def test_replicant_failover(self): result = self.run_command(['block', 'replica-failover', '12345678', - '--replicant-id=5678', '--immediate']) + '--replicant-id=5678']) self.assert_no_fail(result) self.assertEqual('Failover to replicant is now in progress.\n', @@ -506,8 +506,7 @@ def test_replicant_failover_unsuccessful(self, failover_mock): result.output) def test_replicant_failback(self): - result = self.run_command(['block', 'replica-failback', '12345678', - '--replicant-id=5678']) + result = self.run_command(['block', 'replica-failback', '12345678']) self.assert_no_fail(result) self.assertEqual('Failback from replicant is now in progress.\n', @@ -517,8 +516,7 @@ def test_replicant_failback(self): def test_replicant_failback_unsuccessful(self, failback_mock): failback_mock.return_value = False - result = self.run_command(['block', 'replica-failback', '12345678', - '--replicant-id=5678']) + result = self.run_command(['block', 'replica-failback', '12345678']) self.assertEqual('Failback operation could not be initiated.\n', result.output) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index bd5ab9d37..f5b3b4371 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -328,26 +328,25 @@ def test_cancel_snapshot_exception_snapshot_billing_item_not_found(self): ) def test_replicant_failover(self): - result = self.block.failover_to_replicant(1234, 5678, immediate=True) + result = self.block.failover_to_replicant(1234, 5678) self.assertEqual( fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', - args=(5678, True), + args=(5678,), identifier=1234, ) def test_replicant_failback(self): - result = self.block.failback_from_replicant(1234, 5678) + result = self.block.failback_from_replicant(1234) self.assertEqual( fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', - args=(5678,), identifier=1234, ) From d5b544add6ec97f5a1ba2b6b1127e8d64d4bac06 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 19 Dec 2019 16:44:58 -0600 Subject: [PATCH 0214/1582] fixing new flask8 errors that show up out of nowhere --- SoftLayer/__init__.py | 2 +- SoftLayer/shell/core.py | 2 +- SoftLayer/testing/xmlrpc.py | 2 +- tests/CLI/helper_tests.py | 26 +++++++++++++------------- tests/managers/hardware_tests.py | 3 +-- tests/managers/vs/vs_capacity_tests.py | 1 - 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/SoftLayer/__init__.py b/SoftLayer/__init__.py index 04ba36aaa..9e14ea38e 100644 --- a/SoftLayer/__init__.py +++ b/SoftLayer/__init__.py @@ -29,7 +29,7 @@ __author__ = 'SoftLayer Technologies, Inc.' __license__ = 'MIT' __copyright__ = 'Copyright 2016 SoftLayer Technologies, Inc.' -__all__ = [ +__all__ = [ # noqa: F405 'BaseClient', 'create_client_from_env', 'Client', diff --git a/SoftLayer/shell/core.py b/SoftLayer/shell/core.py index 8946815e2..3762ed833 100644 --- a/SoftLayer/shell/core.py +++ b/SoftLayer/shell/core.py @@ -81,7 +81,7 @@ def cli(ctx, env): return except ShellExit: return - except Exception as ex: + except Exception: env.vars['last_exit_code'] = 1 traceback.print_exc(file=sys.stderr) diff --git a/SoftLayer/testing/xmlrpc.py b/SoftLayer/testing/xmlrpc.py index 1e3bf5f1d..208c1cb11 100644 --- a/SoftLayer/testing/xmlrpc.py +++ b/SoftLayer/testing/xmlrpc.py @@ -83,7 +83,7 @@ def do_POST(self): allow_none=True, methodresponse=True) self.wfile.write(response_body.encode('utf-8')) - except Exception as ex: + except Exception: self.send_response(500) logging.exception("Error while handling request") diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 6da71c7d2..5c6656c21 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -154,29 +154,29 @@ def test_sort(self): class FormattedListTests(testing.TestCase): def test_init(self): - l = formatting.listing([1, 'two'], separator=':') - self.assertEqual([1, 'two'], list(l)) - self.assertEqual(':', l.separator) + listing = formatting.listing([1, 'two'], separator=':') + self.assertEqual([1, 'two'], list(listing)) + self.assertEqual(':', listing.separator) - l = formatting.listing([]) - self.assertEqual(',', l.separator) + listing = formatting.listing([]) + self.assertEqual(',', listing.separator) def test_to_python(self): - l = formatting.listing([1, 'two']) - result = l.to_python() + listing = formatting.listing([1, 'two']) + result = listing.to_python() self.assertEqual([1, 'two'], result) - l = formatting.listing(x for x in [1, 'two']) - result = l.to_python() + listing = formatting.listing(x for x in [1, 'two']) + result = listing.to_python() self.assertEqual([1, 'two'], result) def test_str(self): - l = formatting.listing([1, 'two']) - result = str(l) + listing = formatting.listing([1, 'two']) + result = str(listing) self.assertEqual('1,two', result) - l = formatting.listing((x for x in [1, 'two']), separator=':') - result = str(l) + listing = formatting.listing((x for x in [1, 'two']), separator=':') + result = str(listing) self.assertEqual('1:two', result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index fbbdb2218..5710dd0ae 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -281,8 +281,7 @@ def test_cancel_hardware(self): def test_cancel_hardware_no_billing_item(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') - mock.return_value = {'id': 987, 'openCancellationTicket': {'id': 1234}, - 'openCancellationTicket': {'id': 1234}} + mock.return_value = {'id': 987, 'openCancellationTicket': {'id': 1234}} ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.cancel_hardware, diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index 5229ebec4..bb7178055 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -132,7 +132,6 @@ def test_create_guest(self): 'maxMemory': None, 'hostname': 'A1538172419', 'domain': 'test.com', - 'localDiskFlag': None, 'hourlyBillingFlag': True, 'supplementalCreateObjectOptions': { 'bootMode': None, From 42959fc6bbbc0867606c1e8d1f7f8e5283303005 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 20 Dec 2019 12:09:14 -0400 Subject: [PATCH 0215/1582] Order a virtual server private. --- SoftLayer/managers/ordering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index cc9a6344b..e86679f61 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -416,7 +416,7 @@ def get_item_capacity(self, items, item_keynames): for item_keyname in item_keynames: for item in items: if item['keyName'] == item_keyname: - if "GUEST_CORE" in item["keyName"]: + if "CORE" in item["keyName"]: item_capacity = item['capacity'] break if "TIER" in item["keyName"]: From 5548224d67ac06276353c7b170164e6d6d7d7c5e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 20 Dec 2019 10:53:40 -0600 Subject: [PATCH 0216/1582] v5.8.4 release --- CHANGELOG.md | 7 +++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 389018f52..2ff0afc50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log +## [5.8.5] - 2019-12-20 +https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 + +- #1199 Fix block storage failback and failover. +- #1202 Order a virtual server private. + + ## [5.8.3] - 2019-12-11 https://github.com/softlayer/softlayer-python/compare/v5.8.2...v5.8.3 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index b25cff026..89bcf5187 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.3' +VERSION = 'v5.8.4' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 814d43269..e254b25c7 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.3', + version='5.8.4', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 867d58c4959bf0677907c0ac44a8cb6e97a0dfd1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 20 Dec 2019 13:28:45 -0400 Subject: [PATCH 0217/1582] Fix File Storage failback and failover. --- SoftLayer/CLI/file/replication/failback.py | 8 ++------ SoftLayer/CLI/file/replication/failover.py | 9 ++------- SoftLayer/managers/file.py | 11 ++++------- tests/CLI/modules/file_tests.py | 8 +++----- tests/managers/file_tests.py | 7 +++---- 5 files changed, 14 insertions(+), 29 deletions(-) diff --git a/SoftLayer/CLI/file/replication/failback.py b/SoftLayer/CLI/file/replication/failback.py index 6fc2e9b76..d56142b10 100644 --- a/SoftLayer/CLI/file/replication/failback.py +++ b/SoftLayer/CLI/file/replication/failback.py @@ -9,16 +9,12 @@ @click.command() @click.argument('volume-id') -@click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env -def cli(env, volume_id, replicant_id): +def cli(env, volume_id): """Failback a file volume from the given replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - success = file_storage_manager.failback_from_replicant( - volume_id, - replicant_id - ) + success = file_storage_manager.failback_from_replicant(volume_id) if success: click.echo("Failback from replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/failover.py b/SoftLayer/CLI/file/replication/failover.py index d5695fb54..8ceedd080 100644 --- a/SoftLayer/CLI/file/replication/failover.py +++ b/SoftLayer/CLI/file/replication/failover.py @@ -9,19 +9,14 @@ @click.command() @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") -@click.option('--immediate', - is_flag=True, - default=False, - help="Failover to replicant immediately.") @environment.pass_env -def cli(env, volume_id, replicant_id, immediate): +def cli(env, volume_id, replicant_id): """Failover a file volume to the given replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) success = file_storage_manager.failover_to_replicant( volume_id, - replicant_id, - immediate + replicant_id ) if success: diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index b6d16053f..c0d8fcaee 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -496,25 +496,22 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal reason, id=billing_item_id) - def failover_to_replicant(self, volume_id, replicant_id, immediate=False): + def failover_to_replicant(self, volume_id, replicant_id): """Failover to a volume replicant. :param integer volume_id: The ID of the volume :param integer replicant_id: ID of replicant to failover to - :param boolean immediate: Flag indicating if failover is immediate :return: Returns whether failover was successful or not """ return self.client.call('Network_Storage', 'failoverToReplicant', - replicant_id, immediate, id=volume_id) + replicant_id, id=volume_id) - def failback_from_replicant(self, volume_id, replicant_id): + def failback_from_replicant(self, volume_id): """Failback from a volume replicant. :param integer volume_id: The ID of the volume - :param integer replicant_id: ID of replicant to failback from :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', - replicant_id, id=volume_id) + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 465e9ec03..f64b19624 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -444,7 +444,7 @@ def test_snapshot_cancel(self): def test_replicant_failover(self): result = self.run_command(['file', 'replica-failover', '12345678', - '--replicant-id=5678', '--immediate']) + '--replicant-id=5678']) self.assert_no_fail(result) self.assertEqual('Failover to replicant is now in progress.\n', @@ -461,8 +461,7 @@ def test_replicant_failover_unsuccessful(self, failover_mock): result.output) def test_replicant_failback(self): - result = self.run_command(['file', 'replica-failback', '12345678', - '--replicant-id=5678']) + result = self.run_command(['file', 'replica-failback', '12345678']) self.assert_no_fail(result) self.assertEqual('Failback from replicant is now in progress.\n', @@ -472,8 +471,7 @@ def test_replicant_failback(self): def test_replicant_failback_unsuccessful(self, failback_mock): failback_mock.return_value = False - result = self.run_command(['file', 'replica-failback', '12345678', - '--replicant-id=5678']) + result = self.run_command(['file', 'replica-failback', '12345678']) self.assertEqual('Failback operation could not be initiated.\n', result.output) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 08658b6a9..9e69d7fcb 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -274,26 +274,25 @@ def test_cancel_snapshot_exception_snapshot_billing_item_not_found(self): ) def test_replicant_failover(self): - result = self.file.failover_to_replicant(1234, 5678, immediate=True) + result = self.file.failover_to_replicant(1234, 5678) self.assertEqual( fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', - args=(5678, True), + args=(5678,), identifier=1234, ) def test_replicant_failback(self): - result = self.file.failback_from_replicant(1234, 5678) + result = self.file.failback_from_replicant(1234) self.assertEqual( fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', - args=(5678,), identifier=1234, ) From 99860844eb7c92ec919ceb7ce2f2a14434b2f6f6 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Dec 2019 19:24:55 -0400 Subject: [PATCH 0218/1582] #1195 Remove isGatewayAddress property of record template --- SoftLayer/managers/dns.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index a3fc322af..99545944f 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -226,6 +226,7 @@ def edit_record(self, record): :param dict record: the record to update """ + record.pop('isGatewayAddress', None) self.record.editObject(record, id=record['id']) def dump_zone(self, zone_id): From b51572a92d848482072026dd9cf95f634ca52656 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 18:36:01 -0400 Subject: [PATCH 0219/1582] #1195 Added hardware dns-sync command --- SoftLayer/CLI/hardware/dns.py | 152 ++++++++++++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + 2 files changed, 153 insertions(+) create mode 100644 SoftLayer/CLI/hardware/dns.py diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py new file mode 100644 index 000000000..19e611818 --- /dev/null +++ b/SoftLayer/CLI/hardware/dns.py @@ -0,0 +1,152 @@ +"""Sync DNS records.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command(epilog="""If you don't specify any +arguments, it will attempt to update both the A and PTR records. If you don't +want to update both records, you may use the -a or --ptr arguments to limit +the records updated.""") +@click.argument('identifier') +@click.option('--a-record', '-a', + is_flag=True, + help="Sync the A record for the host") +@click.option('--aaaa-record', + is_flag=True, + help="Sync the AAAA record for the host") +@click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") +@click.option('--ttl', + default=7200, + show_default=True, + type=click.INT, + help="Sets the TTL for the A and/or PTR records") +@environment.pass_env +def cli(env, identifier, a_record, aaaa_record, ptr, ttl): + """Sync DNS records.""" + + items = ['id', + 'globalIdentifier', + 'fullyQualifiedDomainName', + 'hostname', + 'domain', + 'primaryBackendIpAddress', + 'primaryIpAddress', + '''primaryNetworkComponent[ + id, primaryIpAddress, + primaryVersion6IpAddressRecord[ipAddress] + ]'''] + mask = "mask[%s]" % ','.join(items) + dns = SoftLayer.DNSManager(env.client) + server = SoftLayer.HardwareManager(env.client) + + hw_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_hardware(hw_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, + instance['domain'], + name='zone') + + def sync_a_record(): + """Sync A record.""" + records = dns.get_records(zone_id, + host=instance['hostname'], + record_type='a') + if not records: + # don't have a record, lets add one to the base zone + dns.create_record(zone['id'], + instance['hostname'], + 'a', + instance['primaryIpAddress'], + ttl=ttl) + else: + if len(records) != 1: + raise exceptions.CLIAbort("Aborting A record sync, found " + "%d A record exists!" % len(records)) + rec = records[0] + rec['data'] = instance['primaryIpAddress'] + rec['ttl'] = ttl + dns.edit_record(rec) + + def sync_aaaa_record(): + """Sync AAAA record.""" + records = dns.get_records(zone_id, + host=instance['hostname'], + record_type='aaaa') + try: + # done this way to stay within 80 character lines + component = instance['primaryNetworkComponent'] + record = component['primaryVersion6IpAddressRecord'] + ip_address = record['ipAddress'] + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" + % instance['fullyQualifiedDomainName']) + + if not records: + # don't have a record, lets add one to the base zone + dns.create_record(zone['id'], + instance['hostname'], + 'aaaa', + ip_address, + ttl=ttl) + else: + if len(records) != 1: + raise exceptions.CLIAbort("Aborting A record sync, found " + "%d A record exists!" % len(records)) + rec = records[0] + rec['data'] = ip_address + rec['ttl'] = ttl + dns.edit_record(rec) + + def sync_ptr_record(): + """Sync PTR record.""" + host_rec = instance['primaryIpAddress'].split('.')[-1] + ptr_domains = (env.client['Hardware_Server'] + .getReverseDomainRecords(id=instance['id'])[0]) + edit_ptr = None + for ptr in ptr_domains['resourceRecords']: + if ptr['host'] == host_rec: + ptr['ttl'] = ttl + edit_ptr = ptr + break + + if edit_ptr: + edit_ptr['data'] = instance['fullyQualifiedDomainName'] + dns.edit_record(edit_ptr) + else: + dns.create_record(ptr_domains['id'], + host_rec, + 'ptr', + instance['fullyQualifiedDomainName'], + ttl=ttl) + + if not instance['primaryIpAddress']: + raise exceptions.CLIAbort('No primary IP address associated with ' + 'this VS') + + zone = dns.get_zone(zone_id) + + go_for_it = env.skip_confirmations or formatting.confirm( + "Attempt to update DNS records for %s" + % instance['fullyQualifiedDomainName']) + + if not go_for_it: + raise exceptions.CLIAbort("Aborting DNS sync") + + both = False + if not ptr and not a_record and not aaaa_record: + both = True + + if both or a_record: + sync_a_record() + + if both or ptr: + sync_ptr_record() + + if aaaa_record: + sync_aaaa_record() diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5c37541df..97f004bcd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -234,6 +234,7 @@ ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), + ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), From 65237ea57c1551d3be27ea843a3222665386ffc0 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 18:36:58 -0400 Subject: [PATCH 0220/1582] #1195 Added hardware dns-sync command unittests --- tests/CLI/modules/server_tests.py | 188 ++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 29ec65d40..7f000c8eb 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -638,3 +638,191 @@ def test_bandwidth_hw_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_both(self, confirm_mock): + confirm_mock.return_value = True + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + getReverseDomainRecords.return_value = [{ + 'networkAddress': '172.16.1.100', + 'name': '2.240.16.172.in-addr.arpa', + 'resourceRecords': [{'data': 'test.softlayer.com.', + 'id': 100, + 'host': '12'}], + 'updateDate': '2013-09-11T14:36:57-07:00', + 'serial': 1234665663, + 'id': 123456, + }] + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [] + createAargs = ({ + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 98765, + 'data': '172.16.1.100', + 'ttl': 7200 + },) + createPTRargs = ({ + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) + + result = self.run_command(['hw', 'dns-sync', '1000']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords') + self.assert_called_with('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createAargs) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createPTRargs) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_v6(self, confirm_mock): + confirm_mock.return_value = True + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [] + server = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + test_server = { + 'id': 1000, + 'hostname': 'hardware-test1', + 'domain': 'sftlyr.ws', + 'primaryIpAddress': '172.16.1.100', + 'fullyQualifiedDomainName': 'hw-test1.sftlyr.ws', + "primaryNetworkComponent": {} + } + server.return_value = test_server + + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + test_server['primaryNetworkComponent'] = { + 'primaryVersion6IpAddressRecord': { + 'ipAddress': '2607:f0d0:1b01:0023:0000:0000:0000:0004' + } + } + createV6args = ({ + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 98765, + '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) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'createObject', + args=createV6args) + + v6Record = { + 'id': 1, + 'ttl': 7200, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'host': 'hardware-test1', + 'type': 'aaaa' + } + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [v6Record] + editArgs = (v6Record,) + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [v6Record, v6Record] + result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_edit_a(self, confirm_mock): + confirm_mock.return_value = True + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [ + {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'} + ] + editArgs = ( + {'type': 'a', 'host': 'hardware-test1', 'data': '172.16.1.100', + 'id': 1, 'ttl': 7200}, + ) + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + getResourceRecords = self.set_mock('SoftLayer_Dns_Domain', + 'getResourceRecords') + getResourceRecords.return_value = [ + {'id': 1, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'}, + {'id': 2, 'ttl': 7200, 'data': '1.1.1.1', + 'host': 'hardware-test1', 'type': 'a'} + ] + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_edit_ptr(self, confirm_mock): + confirm_mock.return_value = True + getReverseDomainRecords = self.set_mock('SoftLayer_Hardware_Server', + 'getReverseDomainRecords') + getReverseDomainRecords.return_value = [{ + 'networkAddress': '172.16.1.100', + 'name': '2.240.16.172.in-addr.arpa', + 'resourceRecords': [{'data': 'test.softlayer.com.', + 'id': 123, + 'host': '100'}], + 'updateDate': '2013-09-11T14:36:57-07:00', + 'serial': 1234665663, + 'id': 123456, + }] + editArgs = ({'host': '100', 'data': 'hardware-test1.test.sftlyr.ws', + 'id': 123, 'ttl': 7200},) + result = self.run_command(['hw', 'dns-sync', '--ptr', '1000']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', + 'editObject', + args=editArgs) + + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_dns_sync_misc_exception(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + guest = self.set_mock('SoftLayer_Hardware_Server', 'getObject') + test_guest = { + 'id': 1000, + 'primaryIpAddress': '', + 'hostname': 'hardware-test1', + 'domain': 'sftlyr.ws', + 'fullyQualifiedDomainName': 'hardware-test1.sftlyr.ws', + "primaryNetworkComponent": {} + } + guest.return_value = test_guest + result = self.run_command(['hw', 'dns-sync', '-a', '1000']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) From 00b12f0b79869bb368b5c041801da2fe85812798 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Dec 2019 19:40:45 -0400 Subject: [PATCH 0221/1582] #1195 Fix tox E303 too many blank lines --- tests/CLI/modules/server_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 7f000c8eb..1d2995004 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -805,7 +805,6 @@ def test_dns_sync_edit_ptr(self, confirm_mock): 'editObject', args=editArgs) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_misc_exception(self, confirm_mock): confirm_mock.return_value = False From 27a1d3da719467da15345acf513859642e09497e Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 30 Dec 2019 17:34:50 -0600 Subject: [PATCH 0222/1582] #1205 refactored the dns_sync sub-functions for vs and hw dns-sync --- SoftLayer/CLI/hardware/dns.py | 136 ++++++--------------------------- SoftLayer/CLI/virt/dns.py | 138 ++++++---------------------------- SoftLayer/managers/dns.py | 51 ++++++++++++- 3 files changed, 96 insertions(+), 229 deletions(-) diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index 19e611818..d698265b5 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -1,5 +1,6 @@ """Sync DNS records.""" # :license: MIT, see LICENSE for more details. +# pylint: disable=duplicate-code import click @@ -15,138 +16,49 @@ want to update both records, you may use the -a or --ptr arguments to limit the records updated.""") @click.argument('identifier') -@click.option('--a-record', '-a', - is_flag=True, - help="Sync the A record for the host") -@click.option('--aaaa-record', - is_flag=True, - help="Sync the AAAA record for the host") +@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host") +@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host") @click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") -@click.option('--ttl', - default=7200, - show_default=True, - type=click.INT, +@click.option('--ttl', default=7200, show_default=True, type=click.INT, help="Sets the TTL for the A and/or PTR records") @environment.pass_env def cli(env, identifier, a_record, aaaa_record, ptr, ttl): """Sync DNS records.""" - items = ['id', - 'globalIdentifier', - 'fullyQualifiedDomainName', - 'hostname', - 'domain', - 'primaryBackendIpAddress', - 'primaryIpAddress', - '''primaryNetworkComponent[ - id, primaryIpAddress, - primaryVersion6IpAddressRecord[ipAddress] - ]'''] - mask = "mask[%s]" % ','.join(items) + mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain, + primaryBackendIpAddress,primaryIpAddress, + primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]""" dns = SoftLayer.DNSManager(env.client) server = SoftLayer.HardwareManager(env.client) - hw_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') - instance = server.get_hardware(hw_id, mask=mask) - zone_id = helpers.resolve_id(dns.resolve_ids, - instance['domain'], - name='zone') - - def sync_a_record(): - """Sync A record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='a') - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'a', - instance['primaryIpAddress'], - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = instance['primaryIpAddress'] - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_aaaa_record(): - """Sync AAAA record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='aaaa') - try: - # done this way to stay within 80 character lines - component = instance['primaryNetworkComponent'] - record = component['primaryVersion6IpAddressRecord'] - ip_address = record['ipAddress'] - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" - % instance['fullyQualifiedDomainName']) - - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'aaaa', - ip_address, - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = ip_address - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_ptr_record(): - """Sync PTR record.""" - host_rec = instance['primaryIpAddress'].split('.')[-1] - ptr_domains = (env.client['Hardware_Server'] - .getReverseDomainRecords(id=instance['id'])[0]) - edit_ptr = None - for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: - ptr['ttl'] = ttl - edit_ptr = ptr - break - - if edit_ptr: - edit_ptr['data'] = instance['fullyQualifiedDomainName'] - dns.edit_record(edit_ptr) - else: - dns.create_record(ptr_domains['id'], - host_rec, - 'ptr', - instance['fullyQualifiedDomainName'], - ttl=ttl) + server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_hardware(server_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone') if not instance['primaryIpAddress']: - raise exceptions.CLIAbort('No primary IP address associated with ' - 'this VS') - - zone = dns.get_zone(zone_id) + raise exceptions.CLIAbort('No primary IP address associated with this hardware') go_for_it = env.skip_confirmations or formatting.confirm( - "Attempt to update DNS records for %s" - % instance['fullyQualifiedDomainName']) + "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise exceptions.CLIAbort("Aborting DNS sync") - both = False - if not ptr and not a_record and not aaaa_record: - both = True + # both will be true only if no options are passed in, basically. + both = (not ptr) and (not a_record) and (not aaaa_record) if both or a_record: - sync_a_record() + dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl) if both or ptr: - sync_ptr_record() + # getReverseDomainRecords returns a list of 1 element, so just get the top. + ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: - sync_aaaa_record() + try: + # done this way to stay within 80 character lines + ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] + dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) diff --git a/SoftLayer/CLI/virt/dns.py b/SoftLayer/CLI/virt/dns.py index ca600465d..26b4904cd 100644 --- a/SoftLayer/CLI/virt/dns.py +++ b/SoftLayer/CLI/virt/dns.py @@ -1,5 +1,6 @@ """Sync DNS records.""" # :license: MIT, see LICENSE for more details. +# pylint: disable=duplicate-code import click @@ -15,138 +16,49 @@ want to update both records, you may use the -a or --ptr arguments to limit the records updated.""") @click.argument('identifier') -@click.option('--a-record', '-a', - is_flag=True, - help="Sync the A record for the host") -@click.option('--aaaa-record', - is_flag=True, - help="Sync the AAAA record for the host") +@click.option('--a-record', '-a', is_flag=True, help="Sync the A record for the host") +@click.option('--aaaa-record', is_flag=True, help="Sync the AAAA record for the host") @click.option('--ptr', is_flag=True, help="Sync the PTR record for the host") -@click.option('--ttl', - default=7200, - show_default=True, - type=click.INT, +@click.option('--ttl', default=7200, show_default=True, type=click.INT, help="Sets the TTL for the A and/or PTR records") @environment.pass_env def cli(env, identifier, a_record, aaaa_record, ptr, ttl): """Sync DNS records.""" - items = ['id', - 'globalIdentifier', - 'fullyQualifiedDomainName', - 'hostname', - 'domain', - 'primaryBackendIpAddress', - 'primaryIpAddress', - '''primaryNetworkComponent[ - id, primaryIpAddress, - primaryVersion6IpAddressRecord[ipAddress] - ]'''] - mask = "mask[%s]" % ','.join(items) + mask = """mask[id, globalIdentifier, fullyQualifiedDomainName, hostname, domain, + primaryBackendIpAddress,primaryIpAddress, + primaryNetworkComponent[id,primaryIpAddress,primaryVersion6IpAddressRecord[ipAddress]]]""" dns = SoftLayer.DNSManager(env.client) - vsi = SoftLayer.VSManager(env.client) + server = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') - instance = vsi.get_instance(vs_id, mask=mask) - zone_id = helpers.resolve_id(dns.resolve_ids, - instance['domain'], - name='zone') - - def sync_a_record(): - """Sync A record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='a') - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'a', - instance['primaryIpAddress'], - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = instance['primaryIpAddress'] - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_aaaa_record(): - """Sync AAAA record.""" - records = dns.get_records(zone_id, - host=instance['hostname'], - record_type='aaaa') - try: - # done this way to stay within 80 character lines - component = instance['primaryNetworkComponent'] - record = component['primaryVersion6IpAddressRecord'] - ip_address = record['ipAddress'] - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" - % instance['fullyQualifiedDomainName']) - - if not records: - # don't have a record, lets add one to the base zone - dns.create_record(zone['id'], - instance['hostname'], - 'aaaa', - ip_address, - ttl=ttl) - else: - if len(records) != 1: - raise exceptions.CLIAbort("Aborting A record sync, found " - "%d A record exists!" % len(records)) - rec = records[0] - rec['data'] = ip_address - rec['ttl'] = ttl - dns.edit_record(rec) - - def sync_ptr_record(): - """Sync PTR record.""" - host_rec = instance['primaryIpAddress'].split('.')[-1] - ptr_domains = (env.client['Virtual_Guest'] - .getReverseDomainRecords(id=instance['id'])[0]) - edit_ptr = None - for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: - ptr['ttl'] = ttl - edit_ptr = ptr - break - - if edit_ptr: - edit_ptr['data'] = instance['fullyQualifiedDomainName'] - dns.edit_record(edit_ptr) - else: - dns.create_record(ptr_domains['id'], - host_rec, - 'ptr', - instance['fullyQualifiedDomainName'], - ttl=ttl) + server_id = helpers.resolve_id(server.resolve_ids, identifier, 'VS') + instance = server.get_instance(server_id, mask=mask) + zone_id = helpers.resolve_id(dns.resolve_ids, instance['domain'], name='zone') if not instance['primaryIpAddress']: - raise exceptions.CLIAbort('No primary IP address associated with ' - 'this VS') - - zone = dns.get_zone(zone_id) + raise exceptions.CLIAbort('No primary IP address associated with this VS') go_for_it = env.skip_confirmations or formatting.confirm( - "Attempt to update DNS records for %s" - % instance['fullyQualifiedDomainName']) + "Attempt to update DNS records for %s" % instance['fullyQualifiedDomainName']) if not go_for_it: raise exceptions.CLIAbort("Aborting DNS sync") - both = False - if not ptr and not a_record and not aaaa_record: - both = True + # both will be true only if no options are passed in, basically. + both = (not ptr) and (not a_record) and (not aaaa_record) if both or a_record: - sync_a_record() + dns.sync_host_record(zone_id, instance['hostname'], instance['primaryIpAddress'], 'a', ttl) if both or ptr: - sync_ptr_record() + # getReverseDomainRecords returns a list of 1 element, so just get the top. + ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: - sync_aaaa_record() + try: + # done this way to stay within 80 character lines + ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] + dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) + except KeyError: + raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 99545944f..7635a195d 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -7,6 +7,7 @@ """ import time +from SoftLayer import exceptions from SoftLayer import utils @@ -205,13 +206,11 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, _filter['resourceRecords']['data'] = utils.query_filter(data) if record_type: - _filter['resourceRecords']['type'] = utils.query_filter( - record_type.lower()) + _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) results = self.service.getResourceRecords( id=zone_id, - mask='id,expire,domainId,host,minimum,refresh,retry,' - 'mxPriority,ttl,type,data,responsiblePerson', + mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson', filter=_filter.to_dict(), ) @@ -236,3 +235,47 @@ def dump_zone(self, zone_id): """ return self.service.getZoneFileContents(id=zone_id) + + def sync_host_record(self, zone_id, hostname, ip_address, record_type='a', ttl=7200): + """For a given zone_id, will set hostname's A record to ip_address + + :param integer zone_id: The zone id for the domain + :param string hostname: host part of the record + :param string ip_address: data part of the record + :param integer ttl: TTL for the record + :param string record_type: 'a' or 'aaaa' + """ + records = self.get_records(zone_id, host=hostname, record_type=record_type) + if not records: + # don't have a record, lets add one to the base zone + self.create_record(zone_id, hostname, record_type, ip_address, ttl=ttl) + else: + if len(records) != 1: + raise exceptions.SoftLayerError("Aborting record sync, found %d records!" % len(records)) + rec = records[0] + rec['data'] = ip_address + rec['ttl'] = ttl + self.edit_record(rec) + + def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200): + """Sync PTR record. + + :param dict ptr_domains: result from SoftLayer_Virtual_Guest.getReverseDomainRecords or + SoftLayer_Hardware_Server.getReverseDomainRecords + :param string ip_address: ip address to sync with + :param string fqdn: Fully Qualified Domain Name + :param integer ttl: TTL for the record + """ + host_rec = ip_address.split('.')[-1] + edit_ptr = None + for ptr in ptr_domains['resourceRecords']: + if ptr['host'] == host_rec: + ptr['ttl'] = ttl + edit_ptr = ptr + break + + if edit_ptr: + edit_ptr['data'] = fqdn + self.edit_record(edit_ptr) + else: + self.create_record(ptr_domains['id'], host_rec, 'ptr', fqdn, ttl=ttl) From 64bffc3fe8ca6a01df03c9395ebd9a495836c2cb Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Tue, 31 Dec 2019 11:48:54 -0600 Subject: [PATCH 0223/1582] Fix issue where the summary command fails due to None being provided as the datacenter name. --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/summary.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index f34e0c4f9..3a552df79 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -115,7 +115,7 @@ def cli(env, **kwargs): """Main click CLI entry-point.""" - # Populate environement with client and set it as the context object + # Populate environment with client and set it as the context object env.skip_confirmations = really env.config_file = config env.format = format diff --git a/SoftLayer/CLI/summary.py b/SoftLayer/CLI/summary.py index 6a17a71cf..7b9844de2 100644 --- a/SoftLayer/CLI/summary.py +++ b/SoftLayer/CLI/summary.py @@ -33,12 +33,12 @@ def cli(env, sortby): for name, datacenter in datacenters.items(): table.add_row([ - name, - datacenter['hardware_count'], - datacenter['virtual_guest_count'], - datacenter['vlan_count'], - datacenter['subnet_count'], - datacenter['public_ip_count'], + name or formatting.blank(), + datacenter.get('hardware_count', formatting.blank()), + datacenter.get('virtual_guest_count', formatting.blank()), + datacenter.get('vlan_count', formatting.blank()), + datacenter.get('subnet_count', formatting.blank()), + datacenter.get('public_ip_count', formatting.blank()), ]) env.fout(table) From 47417cc86466bf4b592403dbc69f3222e3a872ef Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 2 Jan 2020 15:24:56 -0600 Subject: [PATCH 0224/1582] #1195 refactored the vs/hw dns-sync commands, moved most of the logic to the managers/dns.py file. Fixed up unit tests to take account of the changes --- SoftLayer/CLI/hardware/dns.py | 2 +- SoftLayer/managers/dns.py | 2 +- tests/CLI/modules/server_tests.py | 15 ++++++++------- tests/CLI/modules/vs/vs_tests.py | 13 +++++++------ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index d698265b5..3b7458003 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -52,7 +52,7 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): if both or ptr: # getReverseDomainRecords returns a list of 1 element, so just get the top. - ptr_domains = env.client['Virtual_Guest'].getReverseDomainRecords(id=instance['id']).pop() + ptr_domains = env.client['Hardware_Server'].getReverseDomainRecords(id=instance['id']).pop() dns.sync_ptr_record(ptr_domains, instance['primaryIpAddress'], instance['fullyQualifiedDomainName'], ttl) if aaaa_record: diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 7635a195d..3a2ea9147 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -269,7 +269,7 @@ def sync_ptr_record(self, ptr_domains, ip_address, fqdn, ttl=7200): host_rec = ip_address.split('.')[-1] edit_ptr = None for ptr in ptr_domains['resourceRecords']: - if ptr['host'] == host_rec: + if ptr.get('host', '') == host_rec: ptr['ttl'] = ttl edit_ptr = ptr break diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 1d2995004..14f8e9201 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -12,6 +12,7 @@ import sys from SoftLayer.CLI import exceptions +from SoftLayer import SoftLayerError from SoftLayer import testing import json @@ -660,7 +661,7 @@ def test_dns_sync_both(self, confirm_mock): createAargs = ({ 'type': 'a', 'host': 'hardware-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '172.16.1.100', 'ttl': 7200 },) @@ -715,7 +716,7 @@ def test_dns_sync_v6(self, confirm_mock): createV6args = ({ 'type': 'aaaa', 'host': 'hardware-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', 'ttl': 7200 },) @@ -748,8 +749,8 @@ def test_dns_sync_v6(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [v6Record, v6Record] result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_a(self, confirm_mock): @@ -779,8 +780,8 @@ def test_dns_sync_edit_a(self, confirm_mock): 'host': 'hardware-test1', 'type': 'a'} ] result = self.run_command(['hw', 'dns-sync', '-a', '1000']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_ptr(self, confirm_mock): @@ -789,7 +790,7 @@ def test_dns_sync_edit_ptr(self, confirm_mock): 'getReverseDomainRecords') getReverseDomainRecords.return_value = [{ 'networkAddress': '172.16.1.100', - 'name': '2.240.16.172.in-addr.arpa', + 'name': '100.1.16.172.in-addr.arpa', 'resourceRecords': [{'data': 'test.softlayer.com.', 'id': 123, 'host': '100'}], diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 203230913..3e57cb209 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -12,6 +12,7 @@ from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest from SoftLayer import SoftLayerAPIError +from SoftLayer import SoftLayerError from SoftLayer import testing @@ -310,7 +311,7 @@ def test_dns_sync_both(self, confirm_mock): createAargs = ({ 'type': 'a', 'host': 'vs-test1', - 'domainId': 98765, + 'domainId': 12345, # from SoftLayer_Account::getDomains 'data': '172.16.240.2', 'ttl': 7200 },) @@ -365,7 +366,7 @@ def test_dns_sync_v6(self, confirm_mock): createV6args = ({ 'type': 'aaaa', 'host': 'vs-test1', - 'domainId': 98765, + 'domainId': 12345, 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', 'ttl': 7200 },) @@ -398,8 +399,8 @@ def test_dns_sync_v6(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [v6Record, v6Record] result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_a(self, confirm_mock): @@ -429,8 +430,8 @@ def test_dns_sync_edit_a(self, confirm_mock): 'host': 'vs-test1', 'type': 'a'} ] result = self.run_command(['vs', 'dns-sync', '-a', '100']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerError) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_edit_ptr(self, confirm_mock): From 633324840b237bdb7f8363a183204795dc8cd5f4 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 6 Jan 2020 17:12:00 -0600 Subject: [PATCH 0225/1582] added docs for slcli hw dns-sync --- docs/cli/hardware.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3e7eeaf4c..08fc273d6 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -94,3 +94,6 @@ This function updates the firmware of a server. If already at the latest version :prog: hw ready :show-nested: +.. click:: SoftLayer.CLI.hardware.dns-sync:cli + :prog: hw dns-sync + :show-nested: From ae84082dfeaaea550ada172d13f9f1c11376ba54 Mon Sep 17 00:00:00 2001 From: Samson Yerraguntla Date: Mon, 13 Jan 2020 15:40:13 -0600 Subject: [PATCH 0226/1582] feature/VolumeLimit Added the functionality for Volume Limit per DataCenter --- SoftLayer/CLI/block/limit.py | 32 +++++++++++++++++++ SoftLayer/CLI/file/limit.py | 32 +++++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ .../fixtures/SoftLayer_Network_Storage.py | 6 ++++ SoftLayer/managers/block.py | 7 ++++ SoftLayer/managers/file.py | 7 ++++ tests/CLI/modules/block_tests.py | 12 +++++++ tests/CLI/modules/file_tests.py | 12 +++++++ tests/managers/block_tests.py | 4 +++ tests/managers/file_tests.py | 4 +++ 10 files changed, 118 insertions(+) create mode 100644 SoftLayer/CLI/block/limit.py create mode 100644 SoftLayer/CLI/file/limit.py diff --git a/SoftLayer/CLI/block/limit.py b/SoftLayer/CLI/block/limit.py new file mode 100644 index 000000000..2c2b0ae4b --- /dev/null +++ b/SoftLayer/CLI/block/limit.py @@ -0,0 +1,32 @@ +"""List number of block storage volumes limit per datacenter.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +DEFAULT_COLUMNS = [ + 'Datacenter', + 'MaximumAvailableCount', + 'ProvisionedCount' +] + + +@click.command() +@click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--sortby', help='Column to sort by', default='Datacenter') +@environment.pass_env +def cli(env, sortby, datacenter): + """List number of block storage volumes limit per datacenter.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + block_volumes = block_manager.list_block_volume_limit() + + table = formatting.KeyValueTable(DEFAULT_COLUMNS) + table.sortby = sortby + for volume in block_volumes: + datacenter_name = volume['datacenterName'] + maximum_available_count = volume['maximumAvailableCount'] + provisioned_count = volume['provisionedCount'] + table.add_row([datacenter_name, maximum_available_count, provisioned_count]) + env.fout(table) \ No newline at end of file diff --git a/SoftLayer/CLI/file/limit.py b/SoftLayer/CLI/file/limit.py new file mode 100644 index 000000000..0876394db --- /dev/null +++ b/SoftLayer/CLI/file/limit.py @@ -0,0 +1,32 @@ +"""List number of file storage volumes limit per datacenter.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + +DEFAULT_COLUMNS = [ + 'Datacenter', + 'MaximumAvailableCount', + 'ProvisionedCount' +] + + +@click.command() +@click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--sortby', help='Column to sort by', default='Datacenter') +@environment.pass_env +def cli(env, sortby, datacenter): + """List number of block storage volumes limit per datacenter.""" + file_manager = SoftLayer.FileStorageManager(env.client) + file_volumes = file_manager.list_file_volume_limit() + + table = formatting.KeyValueTable(DEFAULT_COLUMNS) + table.sortby = sortby + for volume in file_volumes: + datacenter_name = volume['datacenterName'] + maximum_available_count = volume['maximumAvailableCount'] + provisioned_count = volume['provisionedCount'] + table.add_row([datacenter_name, maximum_available_count, provisioned_count]) + env.fout(table) \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5c37541df..113fb8c4e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -102,6 +102,7 @@ ('block:volume-modify', 'SoftLayer.CLI.block.modify:cli'), ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'), + ('block:volume-limit', 'SoftLayer.CLI.block.limit:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -132,6 +133,7 @@ ('file:volume-list', 'SoftLayer.CLI.file.list:cli'), ('file:volume-modify', 'SoftLayer.CLI.file.modify:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), + ('file:volume-limit', 'SoftLayer.CLI.file.limit:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 8a0c97728..3c8d335e9 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -226,3 +226,9 @@ enableSnapshots = True disableSnapshots = True + +getVolumeCountLimits = { + 'datacenterName': 'global', + 'maximumAvailableCount': 300, + 'provisionedCount': 100 +} diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 1f8c6ec5c..fe1b203bc 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -24,6 +24,13 @@ def __init__(self, client): self.configuration = {} self.client = client + def list_block_volume_limit(self): + """Returns a list of block volume count limit. + + :return: Returns a list of block volume count limit. + """ + return self.client.call('Network_Storage', 'getVolumeCountLimits') + def list_block_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of block volumes. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index c0d8fcaee..95cf85c88 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -19,6 +19,13 @@ def __init__(self, client): self.configuration = {} self.client = client + def list_file_volume_limit(self): + """Returns a list of file volume count limit. + + :return: Returns a list of file volume count limit. + """ + return self.client.call('Network_Storage', 'getVolumeCountLimits') + def list_file_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of file volumes. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b8629de8a..a29e7bd8f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -665,3 +665,15 @@ def test_modify_order(self, order_mock): def test_set_password(self): result = self.run_command(['block', 'access-password', '1234', '--password=AAAAA']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.BlockStorageManager.list_block_volume_limit') + def test_volume_limit(self, list_mock): + list_mock.return_value = [ + { + "datacenterName": "global", + "maximumAvailableCount": 300, + "provisionedCount": 100 + }] + + result = self.run_command(['block', 'volume-limit']) + self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index f64b19624..d649b9c6f 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -665,3 +665,15 @@ def test_modify_order(self, order_mock): self.assert_no_fail(result) self.assertEqual('Order #24602 placed successfully!\n > Storage as a Service\n > 1000 GBs\n > 4 IOPS per GB\n', result.output) + + + @mock.patch('SoftLayer.FileStorageManager.list_file_volume_limit') + def test_volume_limit(self, list_mock): + list_mock.return_value = [ + { + "datacenterName": "global", + "maximumAvailableCount": 300, + "provisionedCount": 100 + }] + result = self.run_command(['file', 'volume-limit']) + self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index f5b3b4371..8721cb409 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -936,3 +936,7 @@ def test_setCredentialPassword(self): result = self.block.set_credential_password(access_id=102, password='AAAaaa') self.assertEqual(True, result) self.assert_called_with('SoftLayer_Network_Storage_Allowed_Host', 'setCredentialPassword') + + def test_list_block_volume_limit(self): + result = self.block.list_block_volume_limit() + self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits,result) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 9e69d7fcb..960adc6fb 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -829,3 +829,7 @@ def test_order_file_modified_endurance(self): 'volume': {'id': 102}, 'volumeSize': 1000},) ) + + def test_list_file_volume_limit(self): + result = self.file.list_file_volume_limit() + self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) From f552708f9244a42f6693653006ac760b3b3819e3 Mon Sep 17 00:00:00 2001 From: Samson Yerraguntla Date: Mon, 13 Jan 2020 16:28:57 -0600 Subject: [PATCH 0227/1582] feature/VolumeLimit Address the code style issues --- SoftLayer/CLI/block/limit.py | 5 ++--- SoftLayer/CLI/file/limit.py | 5 ++--- tests/CLI/modules/block_tests.py | 10 +++++----- tests/CLI/modules/file_tests.py | 11 +++++------ tests/managers/block_tests.py | 2 +- tests/managers/cdn_tests.py | 16 ++++++++-------- tests/managers/file_tests.py | 1 - tests/managers/ordering_tests.py | 4 ++-- 8 files changed, 25 insertions(+), 29 deletions(-) diff --git a/SoftLayer/CLI/block/limit.py b/SoftLayer/CLI/block/limit.py index 2c2b0ae4b..13d22c8ce 100644 --- a/SoftLayer/CLI/block/limit.py +++ b/SoftLayer/CLI/block/limit.py @@ -14,10 +14,9 @@ @click.command() -@click.option('--datacenter', '-d', help='Datacenter shortname') @click.option('--sortby', help='Column to sort by', default='Datacenter') @environment.pass_env -def cli(env, sortby, datacenter): +def cli(env, sortby): """List number of block storage volumes limit per datacenter.""" block_manager = SoftLayer.BlockStorageManager(env.client) block_volumes = block_manager.list_block_volume_limit() @@ -29,4 +28,4 @@ def cli(env, sortby, datacenter): maximum_available_count = volume['maximumAvailableCount'] provisioned_count = volume['provisionedCount'] table.add_row([datacenter_name, maximum_available_count, provisioned_count]) - env.fout(table) \ No newline at end of file + env.fout(table) diff --git a/SoftLayer/CLI/file/limit.py b/SoftLayer/CLI/file/limit.py index 0876394db..34dca7563 100644 --- a/SoftLayer/CLI/file/limit.py +++ b/SoftLayer/CLI/file/limit.py @@ -14,10 +14,9 @@ @click.command() -@click.option('--datacenter', '-d', help='Datacenter shortname') @click.option('--sortby', help='Column to sort by', default='Datacenter') @environment.pass_env -def cli(env, sortby, datacenter): +def cli(env, sortby): """List number of block storage volumes limit per datacenter.""" file_manager = SoftLayer.FileStorageManager(env.client) file_volumes = file_manager.list_file_volume_limit() @@ -29,4 +28,4 @@ def cli(env, sortby, datacenter): maximum_available_count = volume['maximumAvailableCount'] provisioned_count = volume['provisionedCount'] table.add_row([datacenter_name, maximum_available_count, provisioned_count]) - env.fout(table) \ No newline at end of file + env.fout(table) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index a29e7bd8f..09c77bbe0 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -669,11 +669,11 @@ def test_set_password(self): @mock.patch('SoftLayer.BlockStorageManager.list_block_volume_limit') def test_volume_limit(self, list_mock): list_mock.return_value = [ - { - "datacenterName": "global", - "maximumAvailableCount": 300, - "provisionedCount": 100 - }] + { + "datacenterName": "global", + "maximumAvailableCount": 300, + "provisionedCount": 100 + }] result = self.run_command(['block', 'volume-limit']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index d649b9c6f..e1bb52515 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -666,14 +666,13 @@ def test_modify_order(self, order_mock): self.assertEqual('Order #24602 placed successfully!\n > Storage as a Service\n > 1000 GBs\n > 4 IOPS per GB\n', result.output) - @mock.patch('SoftLayer.FileStorageManager.list_file_volume_limit') def test_volume_limit(self, list_mock): list_mock.return_value = [ - { - "datacenterName": "global", - "maximumAvailableCount": 300, - "provisionedCount": 100 - }] + { + 'datacenterName': 'global', + 'maximumAvailableCount': 300, + 'provisionedCount': 100 + }] result = self.run_command(['file', 'volume-limit']) self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 8721cb409..280460c20 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -939,4 +939,4 @@ def test_setCredentialPassword(self): def test_list_block_volume_limit(self): result = self.block.list_block_volume_limit() - self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits,result) + self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 31c3c4fa0..32f9ea9e8 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -49,16 +49,16 @@ def test_add_origin(self): cache_query="include all") args = ({ - 'uniqueId': "12345", - 'origin': '10.10.10.1', - 'path': '/example/videos', + 'uniqueId': "12345", + 'origin': '10.10.10.1', + 'path': '/example/videos', 'originType': 'HOST_SERVER', 'header': 'test.example.com', 'httpPort': 80, 'protocol': 'HTTP', 'performanceConfiguration': 'General web delivery', 'cacheKeyQueryRule': "include all" - },) + },) self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', 'createOriginPath', args=args) @@ -69,9 +69,9 @@ def test_add_origin_with_bucket_and_file_extension(self): protocol='http', optimize_for="web", cache_query="include all") args = ({ - 'uniqueId': "12345", - 'origin': '10.10.10.1', - 'path': '/example/videos', + 'uniqueId': "12345", + 'origin': '10.10.10.1', + 'path': '/example/videos', 'originType': 'OBJECT_STORAGE', 'header': 'test.example.com', 'httpPort': 80, @@ -80,7 +80,7 @@ def test_add_origin_with_bucket_and_file_extension(self): 'fileExtension': 'jpg', 'performanceConfiguration': 'General web delivery', 'cacheKeyQueryRule': "include all" - },) + },) self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path', 'createOriginPath', args=args) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 960adc6fb..d6ca66c68 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -668,7 +668,6 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): def test_order_file_duplicate_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 35d5b819a..045517cd8 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -722,7 +722,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -739,7 +739,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) From ace018df0cae447d0d6e997d16748eccead976af Mon Sep 17 00:00:00 2001 From: Samson Yerraguntla Date: Mon, 13 Jan 2020 16:38:54 -0600 Subject: [PATCH 0228/1582] feature/VolumeLimit Change Cli command to Limits --- SoftLayer/CLI/routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 113fb8c4e..cb81c5c53 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -102,7 +102,7 @@ ('block:volume-modify', 'SoftLayer.CLI.block.modify:cli'), ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'), - ('block:volume-limit', 'SoftLayer.CLI.block.limit:cli'), + ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -133,7 +133,7 @@ ('file:volume-list', 'SoftLayer.CLI.file.list:cli'), ('file:volume-modify', 'SoftLayer.CLI.file.modify:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), - ('file:volume-limit', 'SoftLayer.CLI.file.limit:cli'), + ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), From 03c74b122c0e6a1d7e943208eee7c18dcc255b38 Mon Sep 17 00:00:00 2001 From: Samson Yerraguntla Date: Mon, 13 Jan 2020 16:46:09 -0600 Subject: [PATCH 0229/1582] feature/VolumeLimit Fix the unit tests --- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 09c77bbe0..47ffda2b4 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -675,5 +675,5 @@ def test_volume_limit(self, list_mock): "provisionedCount": 100 }] - result = self.run_command(['block', 'volume-limit']) + result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index e1bb52515..0fc4ffc06 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -674,5 +674,5 @@ def test_volume_limit(self, list_mock): 'maximumAvailableCount': 300, 'provisionedCount': 100 }] - result = self.run_command(['file', 'volume-limit']) + result = self.run_command(['file', 'volume-limits']) self.assert_no_fail(result) From f080371963b772b976827f003752bf229d553699 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 13 Jan 2020 19:09:41 -0600 Subject: [PATCH 0230/1582] Add testing/CI for python 3.8 --- .travis.yml | 2 ++ README.rst | 2 +- setup.py | 1 + tox.ini | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d3cc13a76..e40cb2a51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,8 @@ matrix: env: TOX_ENV=py36 - python: "3.7" env: TOX_ENV=py37 + - python: "3.8" + env: TOX_ENV=py38 - python: "pypy3.5" env: TOX_ENV=pypy3 - python: "3.6" diff --git a/README.rst b/README.rst index cdc376753..535b54597 100644 --- a/README.rst +++ b/README.rst @@ -124,7 +124,7 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 3.5, 3.6, or 3.7. +* Python 3.5, 3.6, 3.7, or 3.8. * A valid SoftLayer API username and key. * A connection to SoftLayer's private network is required to use our private network API endpoints. diff --git a/setup.py b/setup.py index e254b25c7..65b1d5c44 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tox.ini b/tox.ini index b0d521ac9..22af57229 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,pypy3,analysis,coverage +envlist = py35,py36,py37,py38,pypy3,analysis,coverage [flake8] From b8da33852ee03dde3efbb91d86225048a08b64b1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 23 Jan 2020 14:06:42 -0400 Subject: [PATCH 0231/1582] Fix vs detail. --- SoftLayer/CLI/virt/detail.py | 7 ++++--- SoftLayer/managers/vs.py | 6 ++++-- tests/CLI/modules/vs/vs_tests.py | 11 +++++++++++ tests/managers/vs/vs_tests.py | 18 ++++++++++++++++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index bf93a8342..131117df2 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -126,14 +126,15 @@ def cli(env, identifier, passwords=False, price=False): def _bw_table(bw_data): """Generates a bandwidth useage table""" table = formatting.Table(['Type', 'In GB', 'Out GB', 'Allotment']) - for bw_point in bw_data.get('useage'): + for bw_point in bw_data.get('usage'): bw_type = 'Private' allotment = 'N/A' if bw_point['type']['alias'] == 'PUBLIC_SERVER_BW': bw_type = 'Public' - allotment = utils.lookup(bw_data, 'allotment', 'amount') - if allotment is None: + if not bw_data.get('allotment'): allotment = '-' + else: + allotment = utils.lookup(bw_data, 'allotment', 'amount') table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 24a264b7f..b1391bc3a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1074,8 +1074,10 @@ def get_bandwidth_allocation(self, instance_id): a_mask = "mask[allocation[amount]]" allotment = self.client.call('Virtual_Guest', 'getBandwidthAllotmentDetail', id=instance_id, mask=a_mask) u_mask = "mask[amountIn,amountOut,type]" - useage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) - return {'allotment': allotment.get('allocation'), 'useage': useage} + usage = self.client.call('Virtual_Guest', 'getBillingCycleBandwidthUsage', id=instance_id, mask=u_mask) + if allotment: + return {'allotment': allotment.get('allocation'), 'usage': usage} + return {'allotment': allotment, 'usage': usage} # pylint: disable=inconsistent-return-statements def _get_price_id_for_upgrade(self, package_items, option, value, public=True): diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 3e57cb209..2c79f42bf 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -205,6 +205,17 @@ def test_detail_vs_empty_tag(self): ['example-tag'], ) + def test_detail_vs_empty_allotment(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail') + mock.return_value = None + result = self.run_command(['vs', 'detail', '100']) + + self.assert_no_fail(result) + self.assertEqual( + json.loads(result.output)['Bandwidth'][0]['Allotment'], + '-', + ) + def test_detail_vs_dedicated_host_not_found(self): ex = SoftLayerAPIError('SoftLayer_Exception', 'Not found') mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 14d41966c..47fcaf20d 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -902,12 +902,26 @@ def test_get_bandwidth_allocation(self): self.assert_called_with('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail', identifier=1234) self.assert_called_with('SoftLayer_Virtual_Guest', 'getBillingCycleBandwidthUsage', identifier=1234) self.assertEqual(result['allotment']['amount'], '250') - self.assertEqual(result['useage'][0]['amountIn'], '.448') + self.assertEqual(result['usage'][0]['amountIn'], '.448') def test_get_bandwidth_allocation_no_allotment(self): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail') - mock.return_value = {} + mock.return_value = None result = self.vs.get_bandwidth_allocation(1234) self.assertEqual(None, result['allotment']) + + def test_get_bandwidth_allocation_with_allotment(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBandwidthAllotmentDetail') + mock.return_value = { + "allocationId": 11111, + "id": 22222, + "allocation": { + "amount": "2000" + } + } + + result = self.vs.get_bandwidth_allocation(1234) + + self.assertEqual(2000, int(result['allotment']['amount'])) From 7818cb43e36836e1c31f399fa5a4b4a0f6e7d674 Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Tue, 14 Jan 2020 16:33:52 -0600 Subject: [PATCH 0232/1582] issue#1210 file block storage - adding new feature to list, assign, and remove subnets from/to ACL host record. --- SoftLayer/CLI/block/subnets/__init__.py | 1 + SoftLayer/CLI/block/subnets/assign.py | 31 ++++++++++ SoftLayer/CLI/block/subnets/list.py | 38 ++++++++++++ SoftLayer/CLI/block/subnets/remove.py | 31 ++++++++++ SoftLayer/CLI/routes.py | 3 + .../SoftLayer_Network_Storage_Allowed_Host.py | 60 +++++++++++++++++++ SoftLayer/managers/block.py | 34 +++++++++++ tests/CLI/modules/block_tests.py | 17 ++++++ tests/managers/block_tests.py | 37 ++++++++++++ 9 files changed, 252 insertions(+) create mode 100644 SoftLayer/CLI/block/subnets/__init__.py create mode 100644 SoftLayer/CLI/block/subnets/assign.py create mode 100644 SoftLayer/CLI/block/subnets/list.py create mode 100644 SoftLayer/CLI/block/subnets/remove.py diff --git a/SoftLayer/CLI/block/subnets/__init__.py b/SoftLayer/CLI/block/subnets/__init__.py new file mode 100644 index 000000000..8824c4279 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/__init__.py @@ -0,0 +1 @@ +"""Block Storage Subnets Control.""" diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py new file mode 100644 index 000000000..29a2a2c6b --- /dev/null +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -0,0 +1,31 @@ +"""Assign block storage subnets to the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('access_id') +@click.option('--subnet-id', multiple=True, type=int, + help="ID of the subnets to assign; e.g.: --subnet-id 1234") +@environment.pass_env +def cli(env, access_id, subnet_id): + """Assign block storage subnets to the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + subnets_id = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + assigned_subnets = block_manager.assign_subnets_to_acl(access_id, + subnets_id) + + for subnet in assigned_subnets: + click.echo("Successfully assigned subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + + failed_to_assign_subnets = list(set(subnets_id) - set(assigned_subnets)) + for subnet in failed_to_assign_subnets: + click.echo("Failed to assign subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py new file mode 100644 index 000000000..2bae4f8d3 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/list.py @@ -0,0 +1,38 @@ +"""List block storage assigned subnets for the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +COLUMNS = [ + 'id', + 'createDate', + 'networkIdentifier', + 'cidr' +] + + +@click.command() +@click.argument('access_id') +@environment.pass_env +def cli(env, access_id): + """List block storage assigned subnets for the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + + block_manager = SoftLayer.BlockStorageManager(env.client) + subnets = block_manager.get_subnets_in_acl(access_id) + + table = formatting.Table(COLUMNS) + for subnet in subnets: + row = ["{0}".format(subnet['id']), + "{0}".format(subnet['createDate']), + "{0}".format(subnet['networkIdentifier']), + "{0}".format(subnet['cidr'])] + table.add_row(row) + + env.fout(table) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py new file mode 100644 index 000000000..b7528b789 --- /dev/null +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -0,0 +1,31 @@ +"""Remove block storage subnets for the given host id.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('access_id') +@click.option('--subnet-id', multiple=True, type=int, + help="ID of the subnets to remove; e.g.: --subnet-id 1234") +@environment.pass_env +def cli(env, access_id, subnet_id): + """Remove block storage subnets for the given host id. + + access_id is the allowed_host_id from slcli block access-list + """ + subnets_id = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + removed_subnets = block_manager.remove_subnets_from_acl(access_id, + subnets_id) + + for subnet in removed_subnets: + click.echo("Successfully removed subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + + failed_to_remove_subnets = list(set(subnets_id) - set(removed_subnets)) + for subnet in failed_to_remove_subnets: + click.echo("Failed to remove subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5c37541df..76c9fb77f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -80,6 +80,9 @@ ('block:access-list', 'SoftLayer.CLI.block.access.list:cli'), ('block:access-revoke', 'SoftLayer.CLI.block.access.revoke:cli'), ('block:access-password', 'SoftLayer.CLI.block.access.password:cli'), + ('block:subnets-list', 'SoftLayer.CLI.block.subnets.list:cli'), + ('block:subnets-assign', 'SoftLayer.CLI.block.subnets.assign:cli'), + ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'), ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py index 0582e04b6..5bf8c3354 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py @@ -1 +1,61 @@ +TEST_ALLOWED_HOST = { + 'id': 12345, + 'name': 'Test Allowed Host', + 'accountId': 1234, + 'credentialId': None, + 'createDate': '2020-01-01 00:00:01', + 'iscsiAclCredentials': { + 'id': 129, + 'allowedHostId': 12345, + 'subnetId': 12345678 + }, + 'subnetsInAcl': [{ + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 + }] +} + +getObject = TEST_ALLOWED_HOST + +getSubnetsInAcl = [{ + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 +}] + +assignSubnetsToAcl = [ + 12345678 +] + +removeSubnetsFromAcl = [ + 12345678 +] + setCredentialPassword = True diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 1f8c6ec5c..9ee2a3cf0 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -201,6 +201,40 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) + def assign_subnets_to_acl(self, access_id, subnets_id): + """Assigns subnet records to ACL for the access host. + + :param integer access_id: id of the access host + :param integer subnets_id: The ids of the subnets to be assigned + :return: Returns int array of assigned subnet ids + """ + return self.client.call('Network_Storage_Allowed_Host', + 'assignSubnetsToAcl', + subnets_id, + id=access_id) + + def remove_subnets_from_acl(self, access_id, subnets_id): + """Removes subnet records from ACL for the access host. + + :param integer access_id: id of the access host + :param integer subnets_id: The ids of the subnets to be removed + :return: Returns int array of removed subnet ids + """ + return self.client.call('Network_Storage_Allowed_Host', + 'removeSubnetsFromAcl', + subnets_id, + id=access_id) + + def get_subnets_in_acl(self, access_id): + """Returns a list of subnet records for the access host. + + :param integer access_id: id of the access host + :return: Returns an array of SoftLayer_Network_Subnet objects + """ + return self.client.call('Network_Storage_Allowed_Host', + 'getSubnetsInAcl', + id=access_id) + def get_replication_partners(self, volume_id): """Acquires list of replicant volumes pertaining to the given volume. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b8629de8a..ed2916c56 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -438,6 +438,23 @@ def test_deauthorize_host_to_volume(self): self.assert_no_fail(result) + def test_assign_subnets_to_acl(self): + result = self.run_command(['block', 'subnets-assign', '12345', + '--subnet-id=12345678']) + + self.assert_no_fail(result) + + def test_remove_subnets_from_acl(self): + result = self.run_command(['block', 'subnets-remove', '12345', + '--subnet-id=12345678']) + + self.assert_no_fail(result) + + def test_get_subnets_in_acl(self): + result = self.run_command(['block', 'subnets-list', '12345']) + + self.assert_no_fail(result) + def test_replicant_failover(self): result = self.run_command(['block', 'replica-failover', '12345678', '--replicant-id=5678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index f5b3b4371..8fd56ad77 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -486,6 +486,43 @@ def test_deauthorize_host_to_volume(self): 'removeAccessFromHostList', identifier=50) + def test_assign_subnets_to_acl(self): + result = self.block.assign_subnets_to_acl( + 12345, + subnets_id=[12345678]) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + assignSubnetsToAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'assignSubnetsToAcl', + identifier=12345) + + def test_remove_subnets_from_acl(self): + result = self.block.remove_subnets_from_acl( + 12345, + subnets_id=[12345678]) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + removeSubnetsFromAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'removeSubnetsFromAcl', + identifier=12345) + + def test_get_subnets_in_acl(self): + result = self.block.get_subnets_in_acl(12345) + + self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + getSubnetsInAcl, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage_Allowed_Host', + 'getSubnetsInAcl', + identifier=12345) + def test_create_snapshot(self): result = self.block.create_snapshot(123, 'hello world') From ee1d940d81ed5852234b1ff13ff509fa1a381157 Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Thu, 23 Jan 2020 17:41:49 -0600 Subject: [PATCH 0233/1582] issue#1210 - Correcting formatting and adding input check. --- SoftLayer/CLI/block/subnets/assign.py | 32 ++++++++++++++++----------- SoftLayer/CLI/block/subnets/list.py | 30 +++++++++++++++---------- SoftLayer/CLI/block/subnets/remove.py | 32 ++++++++++++++++----------- SoftLayer/managers/block.py | 18 ++++++++++----- tests/managers/block_tests.py | 4 ++-- 5 files changed, 70 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py index 29a2a2c6b..d349c66ae 100644 --- a/SoftLayer/CLI/block/subnets/assign.py +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -7,25 +7,31 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @click.option('--subnet-id', multiple=True, type=int, help="ID of the subnets to assign; e.g.: --subnet-id 1234") @environment.pass_env def cli(env, access_id, subnet_id): """Assign block storage subnets to the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - subnets_id = list(subnet_id) - block_manager = SoftLayer.BlockStorageManager(env.client) - assigned_subnets = block_manager.assign_subnets_to_acl(access_id, - subnets_id) + try: + subnet_ids = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + assigned_subnets = block_manager.assign_subnets_to_acl(access_id, subnet_ids) - for subnet in assigned_subnets: - click.echo("Successfully assigned subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + for subnet in assigned_subnets: + message = "{0}".format("Successfully assigned subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + click.echo(message) - failed_to_assign_subnets = list(set(subnets_id) - set(assigned_subnets)) - for subnet in failed_to_assign_subnets: - click.echo("Failed to assign subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + failed_to_assign_subnets = list(set(subnet_ids) - set(assigned_subnets)) + for subnet in failed_to_assign_subnets: + message = "{0}".format("Failed to assign subnet id: " + str(subnet) + + ' to allowed host id: ' + str(access_id) + '.') + click.echo(message) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to assign subnets.\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index 2bae4f8d3..7256cac66 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -16,23 +16,29 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @environment.pass_env def cli(env, access_id): """List block storage assigned subnets for the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - block_manager = SoftLayer.BlockStorageManager(env.client) - subnets = block_manager.get_subnets_in_acl(access_id) + try: + block_manager = SoftLayer.BlockStorageManager(env.client) + subnets = block_manager.get_subnets_in_acl(access_id) - table = formatting.Table(COLUMNS) - for subnet in subnets: - row = ["{0}".format(subnet['id']), - "{0}".format(subnet['createDate']), - "{0}".format(subnet['networkIdentifier']), - "{0}".format(subnet['cidr'])] - table.add_row(row) + table = formatting.Table(COLUMNS) + for subnet in subnets: + row = ["{0}".format(subnet['id']), + "{0}".format(subnet['createDate']), + "{0}".format(subnet['networkIdentifier']), + "{0}".format(subnet['cidr'])] + table.add_row(row) - env.fout(table) + env.fout(table) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to list assigned subnets for access-id: " + + str(access_id) + ".\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py index b7528b789..2a77b42ff 100644 --- a/SoftLayer/CLI/block/subnets/remove.py +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -7,25 +7,31 @@ @click.command() -@click.argument('access_id') +@click.argument('access_id', type=int) @click.option('--subnet-id', multiple=True, type=int, help="ID of the subnets to remove; e.g.: --subnet-id 1234") @environment.pass_env def cli(env, access_id, subnet_id): """Remove block storage subnets for the given host id. - access_id is the allowed_host_id from slcli block access-list + access_id is the host_id obtained by: slcli block access-list """ - subnets_id = list(subnet_id) - block_manager = SoftLayer.BlockStorageManager(env.client) - removed_subnets = block_manager.remove_subnets_from_acl(access_id, - subnets_id) + try: + subnet_ids = list(subnet_id) + block_manager = SoftLayer.BlockStorageManager(env.client) + removed_subnets = block_manager.remove_subnets_from_acl(access_id, subnet_ids) - for subnet in removed_subnets: - click.echo("Successfully removed subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + for subnet in removed_subnets: + message = "{0}".format("Successfully removed subnet id: " + str(subnet) + + ' for allowed host id: ' + str(access_id) + '.') + click.echo(message) - failed_to_remove_subnets = list(set(subnets_id) - set(removed_subnets)) - for subnet in failed_to_remove_subnets: - click.echo("Failed to remove subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + failed_to_remove_subnets = list(set(subnet_ids) - set(removed_subnets)) + for subnet in failed_to_remove_subnets: + message = "{0}".format("Failed to remove subnet id: " + str(subnet) + + ' for allowed host id: ' + str(access_id) + '.') + click.echo(message) + + except SoftLayer.SoftLayerAPIError as ex: + message = "{0}".format("Unable to remove subnets.\nReason: " + ex.faultString) + click.echo(message) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 9ee2a3cf0..306e0f37f 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -201,33 +201,39 @@ def deauthorize_host_to_volume(self, volume_id, return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id, **kwargs) - def assign_subnets_to_acl(self, access_id, subnets_id): + def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host - :param integer subnets_id: The ids of the subnets to be assigned + :param list subnet_ids: The ids of the subnets to be assigned :return: Returns int array of assigned subnet ids """ return self.client.call('Network_Storage_Allowed_Host', 'assignSubnetsToAcl', - subnets_id, + subnet_ids, id=access_id) - def remove_subnets_from_acl(self, access_id, subnets_id): + def remove_subnets_from_acl(self, access_id, subnet_ids): """Removes subnet records from ACL for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host - :param integer subnets_id: The ids of the subnets to be removed + :param list subnet_ids: The ids of the subnets to be removed :return: Returns int array of removed subnet ids """ return self.client.call('Network_Storage_Allowed_Host', 'removeSubnetsFromAcl', - subnets_id, + subnet_ids, id=access_id) def get_subnets_in_acl(self, access_id): """Returns a list of subnet records for the access host. + access_id is the host_id obtained by: slcli block access-list + :param integer access_id: id of the access host :return: Returns an array of SoftLayer_Network_Subnet objects """ diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 8fd56ad77..06d4d6ce4 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -489,7 +489,7 @@ def test_deauthorize_host_to_volume(self): def test_assign_subnets_to_acl(self): result = self.block.assign_subnets_to_acl( 12345, - subnets_id=[12345678]) + subnet_ids=[12345678]) self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. assignSubnetsToAcl, result) @@ -502,7 +502,7 @@ def test_assign_subnets_to_acl(self): def test_remove_subnets_from_acl(self): result = self.block.remove_subnets_from_acl( 12345, - subnets_id=[12345678]) + subnet_ids=[12345678]) self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. removeSubnetsFromAcl, result) From 816408657d3c45bcd58df7d06269bfcfeb95ce65 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jan 2020 12:24:51 -0400 Subject: [PATCH 0234/1582] 1211 Fix checking against literal empty string --- SoftLayer/managers/storage_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 7cce7671b..80ec60368 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -660,14 +660,14 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, """ # Ensure the primary volume and snapshot space are not set for cancellation if 'billingItem' not in volume\ - or volume['billingItem']['cancellationDate'] != '': + or volume['billingItem'].get('cancellationDate'): raise exceptions.SoftLayerError( 'This volume is set for cancellation; ' 'unable to order replicant volume') for child in volume['billingItem']['activeChildren']: if child['categoryCode'] == 'storage_snapshot_space'\ - and child['cancellationDate'] != '': + and child.get('cancellationDate'): raise exceptions.SoftLayerError( 'The snapshot space for this volume is set for ' 'cancellation; unable to order replicant volume') From f9359139096241fd5506ce3b9ace92098690e57c Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 Jan 2020 18:00:09 -0600 Subject: [PATCH 0235/1582] #1215 adding in a bunch of CLI documentation --- docs/cli/block.rst | 112 +++++++++++++++++++++++++++++++++++++++++ docs/cli/call_api.rst | 9 ---- docs/cli/cdn.rst | 2 +- docs/cli/commands.rst | 17 +++++++ docs/cli/config.rst | 7 ++- docs/cli/dedicated.rst | 33 ++++++++++++ docs/cli/dns.rst | 41 +++++++++++++++ docs/cli/file.rst | 104 ++++++++++++++++++++++++++++++++++++++ docs/cli/firewall.rst | 24 +++++++++ docs/cli/global_ip.rst | 24 +++++++++ docs/cli/hardware.rst | 2 +- docs/dev/cli.rst | 35 +++++++++++++ 12 files changed, 398 insertions(+), 12 deletions(-) create mode 100644 docs/cli/block.rst delete mode 100644 docs/cli/call_api.rst create mode 100644 docs/cli/commands.rst create mode 100644 docs/cli/dedicated.rst create mode 100644 docs/cli/dns.rst create mode 100644 docs/cli/file.rst create mode 100644 docs/cli/firewall.rst create mode 100644 docs/cli/global_ip.rst diff --git a/docs/cli/block.rst b/docs/cli/block.rst new file mode 100644 index 000000000..5ca8140b7 --- /dev/null +++ b/docs/cli/block.rst @@ -0,0 +1,112 @@ +.. _cli_block: + +Block Commands +============== + +.. click:: SoftLayer.CLI.block.access.authorize:cli + :prog: block access-authorize + :show-nested: + +.. click:: SoftLayer.CLI.block.access.list:cli + :prog: block access-list + :show-nested: + +.. click:: SoftLayer.CLI.block.access.revoke:cli + :prog: block access-revoke + :show-nested: + +.. click:: SoftLayer.CLI.block.access.password:cli + :prog: block access-password + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.failback:cli + :prog: block replica-failback + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.failover:cli + :prog: block replica-failover + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.order:cli + :prog: block replica-order + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.partners:cli + :prog: block replica-partners + :show-nested: + +.. click:: SoftLayer.CLI.block.replication.locations:cli + :prog: block replica-locations + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.cancel:cli + :prog: block snapshot-cancel + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.create:cli + :prog: block snapshot-create + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.delete:cli + :prog: block snapshot-delete + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.disable:cli + :prog: block snapshot-disable + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.enable:cli + :prog: block snapshot-enable + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.schedule_list:cli + :prog: block snapshot-schedule-list + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.list:cli + :prog: block snapshot-list + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.order:cli + :prog: block snapshot-order + :show-nested: + +.. click:: SoftLayer.CLI.block.snapshot.restore:cli + :prog: block snapshot-restore + :show-nested: + +.. click:: SoftLayer.CLI.block.cancel:cli + :prog: block volume-cancel + :show-nested: + +.. click:: SoftLayer.CLI.block.count:cli + :prog: block volume-count + :show-nested: + +.. click:: SoftLayer.CLI.block.detail:cli + :prog: block volume-detail + :show-nested: + +.. click:: SoftLayer.CLI.block.duplicate:cli + :prog: block volume-duplicate + :show-nested: + +.. click:: SoftLayer.CLI.block.list:cli + :prog: block volume-list + :show-nested: + +.. click:: SoftLayer.CLI.block.modify:cli + :prog: block volume-modify + :show-nested: + +.. click:: SoftLayer.CLI.block.order:cli + :prog: block volume-order + :show-nested: + +.. click:: SoftLayer.CLI.block.lun:cli + :prog: block volume-set-lun-id + :show-nested: + +.. click:: SoftLayer.CLI.block.limit:cli + :prog: block volume-limits + :show-nested: diff --git a/docs/cli/call_api.rst b/docs/cli/call_api.rst deleted file mode 100644 index e309f16eb..000000000 --- a/docs/cli/call_api.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _cli_call_api: - -Call API -======== - - -.. click:: SoftLayer.CLI.call_api:cli - :prog: call-api - :show-nested: diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst index fce54f731..e334cd6f3 100644 --- a/docs/cli/cdn.rst +++ b/docs/cli/cdn.rst @@ -1,7 +1,7 @@ .. _cli_cdn: Interacting with CDN -============================== +===================== .. click:: SoftLayer.CLI.cdn.detail:cli diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst new file mode 100644 index 000000000..e29b1d0e8 --- /dev/null +++ b/docs/cli/commands.rst @@ -0,0 +1,17 @@ +.. _cli_commands: + +Call API +======== + + +.. click:: SoftLayer.CLI.call_api:cli + :prog: call-api + :show-nested: + + +Shell +===== + +.. click:: SoftLayer.shell.core:cli + :prog: shell + :show-nested: diff --git a/docs/cli/config.rst b/docs/cli/config.rst index b49e5d5ad..dd8bf1a93 100644 --- a/docs/cli/config.rst +++ b/docs/cli/config.rst @@ -1,7 +1,7 @@ .. _cli_config: Config -======== +====== `Creating an IBMID apikey `_ `IBMid for services `_ @@ -16,3 +16,8 @@ Config .. click:: SoftLayer.CLI.config.show:cli :prog: config show :show-nested: + + +.. click:: SoftLayer.CLI.config.setup:cli + :prog: setup + :show-nested: diff --git a/docs/cli/dedicated.rst b/docs/cli/dedicated.rst new file mode 100644 index 000000000..ba11fb536 --- /dev/null +++ b/docs/cli/dedicated.rst @@ -0,0 +1,33 @@ +.. _cli_dedicated: + +Dedicated Host Commands +======================= + + +.. click:: SoftLayer.CLI.dedicatedhost.list:cli + :prog: dedicatedhost list + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.create:cli + :prog: dedicatedhost create + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.create_options:cli + :prog: dedicatedhost create-options + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.detail:cli + :prog: dedicatedhost detail + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.cancel:cli + :prog: dedicatedhost cancel + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.cancel_guests:cli + :prog: dedicatedhost cancel-guests + :show-nested: + +.. click:: SoftLayer.CLI.dedicatedhost.list_guests:cli + :prog: dedicatedhost list-guests + :show-nested: diff --git a/docs/cli/dns.rst b/docs/cli/dns.rst new file mode 100644 index 000000000..c62bb7ada --- /dev/null +++ b/docs/cli/dns.rst @@ -0,0 +1,41 @@ +.. _cli_dns: + +DNS Management +============== + + +.. click:: SoftLayer.CLI.dns.zone_import:cli + :prog: dns import + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_add:cli + :prog: dns record-add + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_edit:cli + :prog: dns record-edit + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_list:cli + :prog: dns record-list + :show-nested: + +.. click:: SoftLayer.CLI.dns.record_remove:cli + :prog: dns record-remove + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_create:cli + :prog: dns zone-create + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_delete:cli + :prog: dns zone-delete + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_list:cli + :prog: dns zone-list + :show-nested: + +.. click:: SoftLayer.CLI.dns.zone_print:cli + :prog: dns zone-print + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst new file mode 100644 index 000000000..ad01b0337 --- /dev/null +++ b/docs/cli/file.rst @@ -0,0 +1,104 @@ +.. _cli_file: + +File Commands +============= + +.. click:: SoftLayer.CLI.file.access.authorize:cli + :prog: file access-authorize + :show-nested: + +.. click:: SoftLayer.CLI.file.access.list:cli + :prog: file access-list + :show-nested: + +.. click:: SoftLayer.CLI.file.access.revoke:cli + :prog: file access-revoke + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.failback:cli + :prog: file replica-failback + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.failover:cli + :prog: file replica-failover + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.order:cli + :prog: file replica-order + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.partners:cli + :prog: file replica-partners + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.locations:cli + :prog: file replica-locations + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.cancel:cli + :prog: file snapshot-cancel + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.create:cli + :prog: file snapshot-create + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.delete:cli + :prog: file snapshot-delete + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.disable:cli + :prog: file snapshot-disable + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.enable:cli + :prog: file snapshot-enable + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.list:cli + :prog: file snapshot-list + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.order:cli + :prog: file snapshot-order + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.restore:cli + :prog: file snapshot-restore + :show-nested: + +.. click:: SoftLayer.CLI.file.cancel:cli + :prog: file volume-cancel + :show-nested: + +.. click:: SoftLayer.CLI.file.count:cli + :prog: file volume-count + :show-nested: + +.. click:: SoftLayer.CLI.file.detail:cli + :prog: file volume-detail + :show-nested: + +.. click:: SoftLayer.CLI.file.duplicate:cli + :prog: file volume-duplicate + :show-nested: + +.. click:: SoftLayer.CLI.file.list:cli + :prog: file volume-list + :show-nested: + +.. click:: SoftLayer.CLI.file.modify:cli + :prog: file volume-modify + :show-nested: + +.. click:: SoftLayer.CLI.file.order:cli + :prog: file volume-order + :show-nested: + +.. click:: SoftLayer.CLI.file.limit:cli + :prog: file volume-limits + :show-nested: + +.. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli + :prog: file snapshot-schedule-list + :show-nested: diff --git a/docs/cli/firewall.rst b/docs/cli/firewall.rst new file mode 100644 index 000000000..bae1c99d7 --- /dev/null +++ b/docs/cli/firewall.rst @@ -0,0 +1,24 @@ +.. _cli_firewall: + +Firewall Management +=================== + +.. click:: SoftLayer.CLI.firewall.add:cli + :prog: firewall add + :show-nested: + +.. click:: SoftLayer.CLI.firewall.cancel:cli + :prog: firewall cancel + :show-nested: + +.. click:: SoftLayer.CLI.firewall.detail:cli + :prog: firewall detail + :show-nested: + +.. click:: SoftLayer.CLI.firewall.edit:cli + :prog: firewall edit + :show-nested: + +.. click:: SoftLayer.CLI.firewall.list:cli + :prog: firewall list + :show-nested: diff --git a/docs/cli/global_ip.rst b/docs/cli/global_ip.rst new file mode 100644 index 000000000..0e07b4a44 --- /dev/null +++ b/docs/cli/global_ip.rst @@ -0,0 +1,24 @@ +.. _cli_global_ip: + +Global IP Addresses +=================== + +.. click:: SoftLayer.CLI.globalip.assign:cli + :prog: globalip assign + :show-nested: + +.. click:: SoftLayer.CLI.globalip.cancel:cli + :prog: globalip cancel + :show-nested: + +.. click:: SoftLayer.CLI.globalip.create:cli + :prog: globalip create + :show-nested: + +.. click:: SoftLayer.CLI.globalip.list:cli + :prog: globalip list + :show-nested: + +.. click:: SoftLayer.CLI.globalip.unassign:cli + :prog: globalip unassign + :show-nested: diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 08fc273d6..c11402bd3 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -94,6 +94,6 @@ This function updates the firmware of a server. If already at the latest version :prog: hw ready :show-nested: -.. click:: SoftLayer.CLI.hardware.dns-sync:cli +.. click:: SoftLayer.CLI.hardware.dns:cli :prog: hw dns-sync :show-nested: diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index 3962181ac..c1922b268 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -133,3 +133,38 @@ When a confirmation fails, you probably want to stop execution and give a non-ze :: raise CLIAbort("Aborting. Failed confirmation") + + + +Documenting Commands +-------------------- + +All commands should be documented, luckily there is a sphinx module that makes this pretty easy. + +If you were adding a summary command to `slcli account` you would find the documentation in `docs/cli/account.rst` and you would just need to add this for your command + +``` +.. click:: SoftLayer.CLI.account.summary:cli + :prog: account summary + :show-nested: +``` + + +The following REGEX can take the route entry and turn it into a document entry. + +``` +s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ +``` + +FIND: +``` +^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ +``` + +REPLACE: +``` +.. click:: $3 + :prog: $1 $2 + :show-nested: + +``` From 906f11baa585f840f72c70c1a84ed2f60a63f059 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Fri, 24 Jan 2020 18:03:01 -0600 Subject: [PATCH 0236/1582] adding comment about trying to use sphinx-click to auto document everything and how that didn't work --- docs/dev/cli.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index c1922b268..31853174e 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -168,3 +168,7 @@ REPLACE: :show-nested: ``` + + +I tried to get sphinx-click to auto document the ENTIRE slcli, but the results were all on one page, and required a few changes to sphinx-click itself to work. This is due to the fact that most commands in SLCLI use the function name "cli", and some hacks would have to be put inplace to use the path name instead. + From 90ecc36c4c148ea9bc165e399f687dd985dd2f77 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Jan 2020 11:00:29 -0400 Subject: [PATCH 0237/1582] fix the issue 1217 about globalip create ipv6 --- SoftLayer/CLI/globalip/create.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/globalip/create.py b/SoftLayer/CLI/globalip/create.py index a47c1eb78..339d290c7 100644 --- a/SoftLayer/CLI/globalip/create.py +++ b/SoftLayer/CLI/globalip/create.py @@ -10,7 +10,7 @@ @click.command() -@click.option('--v6', '--ipv6', is_flag=True, help='Order a IPv6 IP') +@click.option('-v6', '--ipv6', is_flag=True, help='Order a IPv6 IP') @click.option('--test', help='test order') @environment.pass_env def cli(env, ipv6, test): From 54943d3059b7f46e980411fa93f6bbd45cd226dd Mon Sep 17 00:00:00 2001 From: Osbel Rosales Date: Mon, 27 Jan 2020 16:49:00 -0600 Subject: [PATCH 0238/1582] issue#1210 - Adding doc notes describing ISCSI Isolation must be enabled for new commands to work. --- SoftLayer/CLI/block/subnets/assign.py | 10 +++++----- SoftLayer/CLI/block/subnets/list.py | 3 +-- SoftLayer/CLI/block/subnets/remove.py | 10 +++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/block/subnets/assign.py b/SoftLayer/CLI/block/subnets/assign.py index d349c66ae..3ff2ab758 100644 --- a/SoftLayer/CLI/block/subnets/assign.py +++ b/SoftLayer/CLI/block/subnets/assign.py @@ -15,6 +15,8 @@ def cli(env, access_id, subnet_id): """Assign block storage subnets to the given host id. access_id is the host_id obtained by: slcli block access-list + + SoftLayer_Account::iscsiisolationdisabled must be False to use this command """ try: subnet_ids = list(subnet_id) @@ -22,16 +24,14 @@ def cli(env, access_id, subnet_id): assigned_subnets = block_manager.assign_subnets_to_acl(access_id, subnet_ids) for subnet in assigned_subnets: - message = "{0}".format("Successfully assigned subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + message = "Successfully assigned subnet id: {} to allowed host id: {}".format(subnet, access_id) click.echo(message) failed_to_assign_subnets = list(set(subnet_ids) - set(assigned_subnets)) for subnet in failed_to_assign_subnets: - message = "{0}".format("Failed to assign subnet id: " + str(subnet) + - ' to allowed host id: ' + str(access_id) + '.') + message = "Failed to assign subnet id: {} to allowed host id: {}".format(subnet, access_id) click.echo(message) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to assign subnets.\nReason: " + ex.faultString) + message = "Unable to assign subnets.\nReason: {}".format(ex.faultString) click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index 7256cac66..e111de1b3 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -39,6 +39,5 @@ def cli(env, access_id): env.fout(table) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to list assigned subnets for access-id: " + - str(access_id) + ".\nReason: " + ex.faultString) + message = "Unable to list assigned subnets for access-id: {}.\nReason: {}".format(access_id, ex.faultString) click.echo(message) diff --git a/SoftLayer/CLI/block/subnets/remove.py b/SoftLayer/CLI/block/subnets/remove.py index 2a77b42ff..d700fb1dd 100644 --- a/SoftLayer/CLI/block/subnets/remove.py +++ b/SoftLayer/CLI/block/subnets/remove.py @@ -15,6 +15,8 @@ def cli(env, access_id, subnet_id): """Remove block storage subnets for the given host id. access_id is the host_id obtained by: slcli block access-list + + SoftLayer_Account::iscsiisolationdisabled must be False to use this command """ try: subnet_ids = list(subnet_id) @@ -22,16 +24,14 @@ def cli(env, access_id, subnet_id): removed_subnets = block_manager.remove_subnets_from_acl(access_id, subnet_ids) for subnet in removed_subnets: - message = "{0}".format("Successfully removed subnet id: " + str(subnet) + - ' for allowed host id: ' + str(access_id) + '.') + message = "Successfully removed subnet id: {} for allowed host id: {}".format(subnet, access_id) click.echo(message) failed_to_remove_subnets = list(set(subnet_ids) - set(removed_subnets)) for subnet in failed_to_remove_subnets: - message = "{0}".format("Failed to remove subnet id: " + str(subnet) + - ' for allowed host id: ' + str(access_id) + '.') + message = "Failed to remove subnet id: {} for allowed host id: {}".format(subnet, access_id) click.echo(message) except SoftLayer.SoftLayerAPIError as ex: - message = "{0}".format("Unable to remove subnets.\nReason: " + ex.faultString) + message = "Unable to remove subnets.\nReason: {}".format(ex.faultString) click.echo(message) From b7416ff5f93766965ad17cf3b6a9f82b545cb5b6 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 29 Jan 2020 15:43:00 -0600 Subject: [PATCH 0239/1582] #1215 added docs for every CLI command. --- SoftLayer/CLI/metadata.py | 17 +-- SoftLayer/CLI/user/create.py | 9 +- SoftLayer/CLI/user/delete.py | 1 + SoftLayer/CLI/user/edit_details.py | 5 +- SoftLayer/CLI/user/edit_permissions.py | 1 + SoftLayer/managers/autoscale.py | 2 + docs/cli.rst | 169 +++++++++++++------------ docs/cli/commands.rst | 12 ++ docs/cli/image.rst | 28 ++++ docs/cli/loadbal.rst | 2 +- docs/cli/object_storage.rst | 30 +++++ docs/cli/rwhois.rst | 12 ++ docs/cli/security_groups.rst | 56 ++++++++ docs/cli/sshkey.rst | 24 ++++ docs/cli/ssl.rst | 25 ++++ docs/cli/subnet.rst | 24 ++++ docs/cli/tickets.rst | 40 ++++++ docs/cli/users.rst | 103 +++------------ docs/cli/vlan.rst | 12 ++ docs/cli/vs.rst | 167 ++++++++++++------------ docs/cli_directory.rst | 10 ++ docs/dev/cli.rst | 51 +++++--- docs/index.rst | 1 + 23 files changed, 520 insertions(+), 281 deletions(-) create mode 100644 docs/cli/image.rst create mode 100644 docs/cli/object_storage.rst create mode 100644 docs/cli/rwhois.rst create mode 100644 docs/cli/security_groups.rst create mode 100644 docs/cli/sshkey.rst create mode 100644 docs/cli/ssl.rst create mode 100644 docs/cli/subnet.rst create mode 100644 docs/cli/tickets.rst create mode 100644 docs/cli/vlan.rst create mode 100644 docs/cli_directory.rst diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py index 3cc3e384d..26d6f2d48 100644 --- a/SoftLayer/CLI/metadata.py +++ b/SoftLayer/CLI/metadata.py @@ -28,16 +28,13 @@ 'ip': 'primary_ip', } -HELP = """Find details about this machine - -\b -PROP Choices -%s -\b -Examples : -%s -""" % ('*' + '\n*'.join(META_CHOICES), - 'slcli metadata ' + '\nslcli metadata '.join(META_CHOICES)) +HELP = """Find details about the machine making these API calls. + +.. csv-table:: Choices + + {choices} + +""".format(choices="\n ".join(META_CHOICES)) @click.command(help=HELP, diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index 6ab19884a..ccfe55955 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -34,10 +34,13 @@ def cli(env, username, email, password, from_user, template, api_key): """Creates a user Users. - :Example: slcli user create my@email.com -e my@email.com -p generate -a - -t '{"firstName": "Test", "lastName": "Testerson"}' - Remember to set the permissions and access for this new user. + + Example:: + + slcli user create my@email.com -e my@email.com -p generate -a + -t '{"firstName": "Test", "lastName": "Testerson"}' + """ mgr = SoftLayer.UserManager(env.client) diff --git a/SoftLayer/CLI/user/delete.py b/SoftLayer/CLI/user/delete.py index 409c94661..9c00af754 100644 --- a/SoftLayer/CLI/user/delete.py +++ b/SoftLayer/CLI/user/delete.py @@ -17,6 +17,7 @@ def cli(env, identifier): and will eventually be fully removed from the account by an automated internal process. Example: slcli user delete userId + """ mgr = SoftLayer.UserManager(env.client) diff --git a/SoftLayer/CLI/user/edit_details.py b/SoftLayer/CLI/user/edit_details.py index 024421ed0..5e88427a4 100644 --- a/SoftLayer/CLI/user/edit_details.py +++ b/SoftLayer/CLI/user/edit_details.py @@ -22,7 +22,10 @@ def cli(env, user, template): JSON strings should be enclosed in '' and each item should be enclosed in "" - :Example: slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' + Example:: + + slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' + """ mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') diff --git a/SoftLayer/CLI/user/edit_permissions.py b/SoftLayer/CLI/user/edit_permissions.py index e86ee256a..64791d312 100644 --- a/SoftLayer/CLI/user/edit_permissions.py +++ b/SoftLayer/CLI/user/edit_permissions.py @@ -21,6 +21,7 @@ @environment.pass_env def cli(env, identifier, enable, permission, from_user): """Enable or Disable specific permissions.""" + mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'username') result = False diff --git a/SoftLayer/managers/autoscale.py b/SoftLayer/managers/autoscale.py index 40d7ebe80..78fa18e31 100644 --- a/SoftLayer/managers/autoscale.py +++ b/SoftLayer/managers/autoscale.py @@ -99,6 +99,7 @@ def get_virtual_guests(self, identifier, mask=None): :param identifier: SoftLayer_Scale_Group Id :param mask: optional SoftLayer_Scale_Member objectMask + .. _SoftLayer_Scale_Group::getVirtualGuestMembers(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/getVirtualGuestMembers/ """ @@ -109,6 +110,7 @@ def edit(self, identifier, template): :param identifier: SoftLayer_Scale_Group id :param template: `SoftLayer_Scale_Group`_ + .. _SoftLayer_Scale_Group::editObject(): https://sldn.softlayer.com/reference/services/SoftLayer_Scale_Group/editObject/ .. _SoftLayer_Scale_Group: https://sldn.softlayer.com/reference/datatypes/SoftLayer_Scale_Group/ diff --git a/docs/cli.rst b/docs/cli.rst index 7b09fdc17..dc82da29f 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -9,43 +9,39 @@ SoftLayer API bindings for python and how to efficiently make API calls. See the :ref:`usage-examples` section to see how to discover all of the functionality not fully documented here. -.. toctree:: - :maxdepth: 2 - :glob: - - cli/* - .. _config_setup: Configuration Setup ------------------- To update the configuration, you can use `slcli setup`. + :: - $ slcli setup - Username []: username - API Key or Password []: - Endpoint (public|private|custom): public - :..............:..................................................................: - : Name : Value : - :..............:..................................................................: - : Username : username : - : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : - :..............:..................................................................: - Are you sure you want to write settings to "/home/me/.softlayer"? [y/N]: y + $ slcli setup + Username []: username + API Key or Password []: + Endpoint (public|private|custom): public + :..............:..................................................................: + : Name : Value : + :..............:..................................................................: + : Username : username : + : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : + :..............:..................................................................: + Are you sure you want to write settings to "/home/me/.softlayer"? [y/N]: y To check the configuration, you can use `slcli config show`. + :: - $ slcli config show - :..............:..................................................................: - : Name : Value : - :..............:..................................................................: - : Username : username : - : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : - : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : - :..............:..................................................................: + $ slcli config show + :..............:..................................................................: + : Name : Value : + :..............:..................................................................: + : Username : username : + : API Key : oyVmeipYQCNrjVS4rF9bHWV7D75S6pa1fghFl384v7mwRCbHTfuJ8qRORIqoVnha : + : Endpoint URL : https://api.softlayer.com/xmlrpc/v3.1/ : + :..............:..................................................................: If you are using an account created from the https://cloud.ibm.com portal, your username will be literally `apikey`, and use the key provided. `How to create an IBM apikey `_ @@ -57,6 +53,8 @@ To see more about the config file format, see :ref:`config_file`. Usage Examples -------------- To discover the available commands, simply type `slcli`. + + :: $ slcli @@ -112,71 +110,76 @@ To discover the available commands, simply type `slcli`. As you can see, there are a number of commands/sections. To look at the list of subcommands for virtual servers type `slcli vs`. For example: -:: - $ slcli vs - Usage: slcli vs [OPTIONS] COMMAND [ARGS]... - - Virtual Servers. - - Options: - --help Show this message and exit. - - Commands: - cancel Cancel virtual servers. - capture Capture SoftLayer image. - create Order/create virtual servers. - create-options Virtual server order options. - credentials List virtual server credentials. - detail Get details for a virtual server. - dns-sync Sync DNS records. - edit Edit a virtual server's details. - list List virtual servers. - network Manage network settings. - pause Pauses an active virtual server. - power_off Power off an active virtual server. - power_on Power on a virtual server. - ready Check if a virtual server is ready. - reboot Reboot an active virtual server. - reload Reload operating system on a virtual server. - rescue Reboot into a rescue image. - resume Resumes a paused virtual server. - upgrade Upgrade a virtual server. - -Finally, we can make an actual call. Let's list out the virtual servers on our -account by using `slcli vs list`. :: - $ slcli vs list - :.........:............:....................:.......:........:................:..............:....................: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : - :.........:............:....................:.......:........:................:..............:....................: - : 1234567 : sjc01 : test.example.com : 4 : 4G : 12.34.56 : 65.43.21 : - : - :.........:............:....................:.......:........:................:..............:....................: + $ slcli vs + Usage: slcli vs [OPTIONS] COMMAND [ARGS]... + + Virtual Servers. + + Options: + --help Show this message and exit. + + Commands: + cancel Cancel virtual servers. + capture Capture SoftLayer image. + create Order/create virtual servers. + create-options Virtual server order options. + credentials List virtual server credentials. + detail Get details for a virtual server. + dns-sync Sync DNS records. + edit Edit a virtual server's details. + list List virtual servers. + network Manage network settings. + pause Pauses an active virtual server. + power_off Power off an active virtual server. + power_on Power on a virtual server. + ready Check if a virtual server is ready. + reboot Reboot an active virtual server. + reload Reload operating system on a virtual server. + rescue Reboot into a rescue image. + resume Resumes a paused virtual server. + upgrade Upgrade a virtual server. + + +Finally, we can make an actual call. Let's list out the virtual servers on our account by using `slcli vs list`. + + +Example:: + + $ slcli vs list + :.........:............:....................:.......:........:................:..............:....................: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : + :.........:............:....................:.......:........:................:..............:....................: + : 1234567 : sjc01 : test.example.com : 4 : 4G : 12.34.56 : 65.43.21 : - : + :.........:............:....................:.......:........:................:..............:....................: Most commands will take in additional options/arguments. To see all available actions, use `--help`. + + :: - $ slcli vs list --help - Usage: slcli vs list [OPTIONS] - - List virtual servers. - - Options: - --sortby [guid|hostname|primary_ip|backend_ip|datacenter] - Column to sort by - -c, --cpu INTEGER Number of CPU cores - -D, --domain TEXT Domain portion of the FQDN - -d, --datacenter TEXT Datacenter shortname - -H, --hostname TEXT Host portion of the FQDN - -m, --memory INTEGER Memory in mebibytes - -n, --network TEXT Network port speed in Mbps - --hourly Show only hourly instances - --monthly Show only monthly instances - --tags TEXT Show instances that have one of these comma- - separated tags - --help Show this message and exit. + $ slcli vs list --help + Usage: slcli vs list [OPTIONS] + + List virtual servers. + + Options: + --sortby [guid|hostname|primary_ip|backend_ip|datacenter] + Column to sort by + -c, --cpu INTEGER Number of CPU cores + -D, --domain TEXT Domain portion of the FQDN + -d, --datacenter TEXT Datacenter shortname + -H, --hostname TEXT Host portion of the FQDN + -m, --memory INTEGER Memory in mebibytes + -n, --network TEXT Network port speed in Mbps + --hourly Show only hourly instances + --monthly Show only monthly instances + --tags TEXT Show instances that have one of these comma- + separated tags + --help Show this message and exit. diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst index e29b1d0e8..a577adcdb 100644 --- a/docs/cli/commands.rst +++ b/docs/cli/commands.rst @@ -15,3 +15,15 @@ Shell .. click:: SoftLayer.shell.core:cli :prog: shell :show-nested: + + + +MetaData +======== + +Used to retrieve information about the server making the API call. +Can be called with an un-authenticated API call. + +.. click:: SoftLayer.CLI.metadata:cli + :prog: metadata + :show-nested: diff --git a/docs/cli/image.rst b/docs/cli/image.rst new file mode 100644 index 000000000..771abd16c --- /dev/null +++ b/docs/cli/image.rst @@ -0,0 +1,28 @@ +.. _cli_image: + +Disk Image Commands +=================== + +.. click:: SoftLayer.CLI.image.delete:cli + :prog: image delete + :show-nested: + +.. click:: SoftLayer.CLI.image.detail:cli + :prog: image detail + :show-nested: + +.. click:: SoftLayer.CLI.image.edit:cli + :prog: image edit + :show-nested: + +.. click:: SoftLayer.CLI.image.list:cli + :prog: image list + :show-nested: + +.. click:: SoftLayer.CLI.image.import:cli + :prog: image import + :show-nested: + +.. click:: SoftLayer.CLI.image.export:cli + :prog: image export + :show-nested: diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index b1a6a0dcc..cec4200fd 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -23,7 +23,7 @@ LBaaS Commands :prog: loadbal member-add :show-nested: .. click:: SoftLayer.CLI.loadbal.members:remove - :prog: loadbal member-remote + :prog: loadbal member-remove :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:add :prog: loadbal pool-add diff --git a/docs/cli/object_storage.rst b/docs/cli/object_storage.rst new file mode 100644 index 000000000..43bf03bb8 --- /dev/null +++ b/docs/cli/object_storage.rst @@ -0,0 +1,30 @@ +.. _cli_object_storage: + +Object Storage Commands +======================= + + +.. click:: SoftLayer.CLI.object_storage.list_accounts:cli + :prog: object-storage accounts + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.list_endpoints:cli + :prog: object-storage endpoints + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.list:cli + :prog: object-storage credential list + :show-nested: + + +.. click:: SoftLayer.CLI.object_storage.credential.limit:cli + :prog: object-storage credential limit + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.delete:cli + :prog: object-storage credential delete + :show-nested: + +.. click:: SoftLayer.CLI.object_storage.credential.create:cli + :prog: object-storage credential create + :show-nested: diff --git a/docs/cli/rwhois.rst b/docs/cli/rwhois.rst new file mode 100644 index 000000000..10d2004c9 --- /dev/null +++ b/docs/cli/rwhois.rst @@ -0,0 +1,12 @@ +.. _cli_rwhois: + +Reverse Whois Commands +====================== + +.. click:: SoftLayer.CLI.rwhois.edit:cli + :prog: rwhois edit + :show-nested: + +.. click:: SoftLayer.CLI.rwhois.show:cli + :prog: rwhois show + :show-nested: diff --git a/docs/cli/security_groups.rst b/docs/cli/security_groups.rst new file mode 100644 index 000000000..37385882d --- /dev/null +++ b/docs/cli/security_groups.rst @@ -0,0 +1,56 @@ +.. _cli_security_groups: + +Security Groups +=============== + +.. click:: SoftLayer.CLI.securitygroup.list:cli + :prog: securitygroup list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.detail:cli + :prog: securitygroup detail + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.create:cli + :prog: securitygroup create + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.edit:cli + :prog: securitygroup edit + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.delete:cli + :prog: securitygroup delete + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:rule_list + :prog: securitygroup rule-list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:add + :prog: securitygroup rule-add + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:edit + :prog: securitygroup rule-edit + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.rule:remove + :prog: securitygroup rule-remove + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:interface_list + :prog: securitygroup interface-list + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:add + :prog: securitygroup interface-add + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.interface:remove + :prog: securitygroup interface-remove + :show-nested: + +.. click:: SoftLayer.CLI.securitygroup.event_log:get_by_request_id + :prog: securitygroup event-log + :show-nested: diff --git a/docs/cli/sshkey.rst b/docs/cli/sshkey.rst new file mode 100644 index 000000000..d12d2f424 --- /dev/null +++ b/docs/cli/sshkey.rst @@ -0,0 +1,24 @@ +.. _cli_sshkey: + +SSH Keys +======== + +.. click:: SoftLayer.CLI.sshkey.add:cli + :prog: sshkey add + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.remove:cli + :prog: sshkey remove + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.edit:cli + :prog: sshkey edit + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.list:cli + :prog: sshkey list + :show-nested: + +.. click:: SoftLayer.CLI.sshkey.print:cli + :prog: sshkey print + :show-nested: diff --git a/docs/cli/ssl.rst b/docs/cli/ssl.rst new file mode 100644 index 000000000..f8c95f058 --- /dev/null +++ b/docs/cli/ssl.rst @@ -0,0 +1,25 @@ +.. _cli_ssl: + +SSL Certificates +================ + +.. click:: SoftLayer.CLI.ssl.add:cli + :prog: ssl add + :show-nested: + +.. click:: SoftLayer.CLI.ssl.download:cli + :prog: ssl download + :show-nested: + +.. click:: SoftLayer.CLI.ssl.edit:cli + :prog: ssl edit + :show-nested: + +.. click:: SoftLayer.CLI.ssl.list:cli + :prog: ssl list + :show-nested: + +.. click:: SoftLayer.CLI.ssl.remove:cli + :prog: ssl remove + :show-nested: + diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst new file mode 100644 index 000000000..20fce0def --- /dev/null +++ b/docs/cli/subnet.rst @@ -0,0 +1,24 @@ +.. _cli_subnets: + +Subnets +======= + +.. click:: SoftLayer.CLI.subnet.cancel:cli + :prog: subnet cancel + :show-nested: + +.. click:: SoftLayer.CLI.subnet.create:cli + :prog: subnet create + :show-nested: + +.. click:: SoftLayer.CLI.subnet.detail:cli + :prog: subnet detail + :show-nested: + +.. click:: SoftLayer.CLI.subnet.list:cli + :prog: subnet list + :show-nested: + +.. click:: SoftLayer.CLI.subnet.lookup:cli + :prog: subnet lookup + :show-nested: diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst new file mode 100644 index 000000000..ad5f23428 --- /dev/null +++ b/docs/cli/tickets.rst @@ -0,0 +1,40 @@ +.. _cli_tickets: + +Support Tickets +=============== + +.. click:: SoftLayer.CLI.ticket.create:cli + :prog: ticket create + :show-nested: + +.. click:: SoftLayer.CLI.ticket.detail:cli + :prog: ticket detail + :show-nested: + +.. click:: SoftLayer.CLI.ticket.list:cli + :prog: ticket list + :show-nested: + +.. click:: SoftLayer.CLI.ticket.update:cli + :prog: ticket update + :show-nested: + +.. click:: SoftLayer.CLI.ticket.upload:cli + :prog: ticket upload + :show-nested: + +.. click:: SoftLayer.CLI.ticket.subjects:cli + :prog: ticket subjects + :show-nested: + +.. click:: SoftLayer.CLI.ticket.summary:cli + :prog: ticket summary + :show-nested: + +.. click:: SoftLayer.CLI.ticket.attach:cli + :prog: ticket attach + :show-nested: + +.. click:: SoftLayer.CLI.ticket.detach:cli + :prog: ticket detach + :show-nested: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 3c98199a7..5058d0652 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -4,93 +4,32 @@ Users ============= Version 5.6.0 introduces the ability to interact with user accounts from the cli. -.. _cli_user_create: +.. click:: SoftLayer.CLI.user.list:cli + :prog: user list + :show-nested: -user create ------------ -This command will create a user on your account. +.. click:: SoftLayer.CLI.user.detail:cli + :prog: user detail + :show-nested: -Options -^^^^^^^ --e, --email TEXT Email address for this user. Required for creation. [required] --p, --password TEXT Password to set for this user. If no password is provided, user will be sent an email to generate one, which expires in 24 hours. '-p generate' will create a password for you (Requires Python 3.6+). Passwords require 8+ characters, upper and lowercase, a number and a symbol. --u, --from-user TEXT Base user to use as a template for creating this user. Will default to the user running this command. Information provided in --template supersedes this template. --t, --template TEXT A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/ --a, --api-key Create an API key for this user. --h, --help Show this message and exit. +.. click:: SoftLayer.CLI.user.permissions:cli + :prog: user permissions + :show-nested: -:: +.. click:: SoftLayer.CLI.user.edit_permissions:cli + :prog: user edit-permissions + :show-nested: - slcli user create my@email.com -e my@email.com -p generate -a -t '{"firstName": "Test", "lastName": "Testerson"}' +.. click:: SoftLayer.CLI.user.edit_details:cli + :prog: user edit-details + :show-nested: -.. _cli_user_list: +.. click:: SoftLayer.CLI.user.create:cli + :prog: user create + :show-nested: -user list ----------- -This command will list all Active users on the account that your user has access to view. -There is the option to also filter by username +.. click:: SoftLayer.CLI.user.delete:cli + :prog: user delete + :show-nested: -.. _cli_user_detail: - -user detail -------------------- -Gives a variety of details about a specific user. can be a user id, or username. Will always print a basic set of information about the user, but there are a few extra flags to pull in more detailed information. - -user detail -p, --permissions -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the permissions the user has. To see a list of all possible permissions, or to change a user's permissions, see :ref:`cli_user_permissions` - -user detail -h, --hardware -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the Hardware and Dedicated Hosts the user is able to access. - - -user detail -v, --virtual -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Will list the Virtual Guests the user has access to. - -user detail -l, --logins -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Show login history of this user for the last 30 days. IBMId Users will show logins properly, but may not show failed logins. - -user detail -e, --events -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Shows things that are logged in the Event_Log service. Logins, reboots, reloads, and other such actions will show up here. - -.. _cli_user_permissions: - -user permissions -^^^^^^^^^^^^^^^^^^^^^^^ -Will list off all permission keyNames, along with which are assigned to that specific user. - -.. _cli_user_permissions_edit: - -user edit-permissions ---------------------- -Enable or Disable specific permissions. It is possible to set multiple permissions in one command as well. - -:: - - $ slcli user edit-permissions USERID --enable -p TICKET_EDIT -p TICKET_ADD -p TICKET_SEARCH - -Will enable TICKET_EDIT, TICKET_ADD, and TICKET_SEARCH permissions for the USERID - -.. _cli_user_edit_details: - -user edit-details ------------------ -Edit a User's details - -JSON strings should be enclosed in '' and each item should be enclosed in "\" - -:: - - slcli user edit-details testUser -t '{"firstName": "Test", "lastName": "Testerson"}' - -Options -^^^^^^^ - --t, --template TEXT A json string describing `SoftLayer_User_Customer `_ . [required] --h, --help Show this message and exit. - diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst new file mode 100644 index 000000000..1733f40e4 --- /dev/null +++ b/docs/cli/vlan.rst @@ -0,0 +1,12 @@ +.. _cli_vlan: + +VLANs +===== + +.. click:: SoftLayer.CLI.vlan.detail:cli + :prog: vlan detail + :show-nested: + +.. click:: SoftLayer.CLI.vlan.list:cli + :prog: vlan list + :show-nested: diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index f855238d5..afa0f8b2d 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -10,18 +10,19 @@ using SoftLayer's command-line client. .. note:: - The following assumes that the client is already - :ref:`configured with valid SoftLayer credentials`. + The following assumes that the client is already + :ref:`configured with valid SoftLayer credentials`. First, let's list the current virtual servers with `slcli vs list`. + :: - $ slcli vs list - :.....:............:.........................:.......:........:..............:.............:....................:........: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : owner : - :.....:............:.........................:.......:........:..............:.............:....................:........: - :.....:............:.........................:.......:........:..............:.............:....................:........: + $ slcli vs list + :.....:............:.........................:.......:........:..............:.............:....................:........: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : owner : + :.....:............:.........................:.......:........:..............:.............:....................:........: + :.....:............:.........................:.......:........:..............:.............:....................:........: We don't have any virtual servers yet! Let's fix that. Before we can create a virtual server (VS), we need to know what options are available to us: RAM, @@ -32,47 +33,47 @@ Luckily, there's a simple command to show all options: `slcli vs create-options` :: - $ slcli vs create-options - :................................:.................................................................................: - : name : value : - :................................:.................................................................................: - : datacenter : ams01 : - : : ams03 : - : : wdc07 : - : flavors (balanced) : B1_1X2X25 : - : : B1_1X2X25 : - : : B1_1X2X100 : - : cpus (standard) : 1,2,4,8,12,16,32,56 : - : cpus (dedicated) : 1,2,4,8,16,32,56 : - : cpus (dedicated host) : 1,2,4,8,12,16,32,56 : - : memory : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : - : memory (dedicated host) : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : - : os (CENTOS) : CENTOS_5_64 : - : : CENTOS_LATEST_64 : - : os (CLOUDLINUX) : CLOUDLINUX_5_64 : - : : CLOUDLINUX_6_64 : - : : CLOUDLINUX_LATEST : - : : CLOUDLINUX_LATEST_64 : - : os (COREOS) : COREOS_CURRENT_64 : - : : COREOS_LATEST : - : : COREOS_LATEST_64 : - : os (DEBIAN) : DEBIAN_6_64 : - : : DEBIAN_LATEST_64 : - : os (OTHERUNIXLINUX) : OTHERUNIXLINUX_1_64 : - : : OTHERUNIXLINUX_LATEST : - : : OTHERUNIXLINUX_LATEST_64 : - : os (REDHAT) : REDHAT_5_64 : - : : REDHAT_6_64 : - : : REDHAT_7_64 : - : : REDHAT_LATEST : - : : REDHAT_LATEST_64 : - : san disk(0) : 25,100 : - : san disk(2) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : - : local disk(0) : 25,100 : - : local disk(2) : 25,100,150,200,300 : - : local (dedicated host) disk(0) : 25,100 : - : nic (dedicated host) : 100,1000 : - :................................:.................................................................................: + $ slcli vs create-options + :................................:.................................................................................: + : name : value : + :................................:.................................................................................: + : datacenter : ams01 : + : : ams03 : + : : wdc07 : + : flavors (balanced) : B1_1X2X25 : + : : B1_1X2X25 : + : : B1_1X2X100 : + : cpus (standard) : 1,2,4,8,12,16,32,56 : + : cpus (dedicated) : 1,2,4,8,16,32,56 : + : cpus (dedicated host) : 1,2,4,8,12,16,32,56 : + : memory : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : + : memory (dedicated host) : 1024,2048,4096,6144,8192,12288,16384,32768,49152,65536,131072,247808 : + : os (CENTOS) : CENTOS_5_64 : + : : CENTOS_LATEST_64 : + : os (CLOUDLINUX) : CLOUDLINUX_5_64 : + : : CLOUDLINUX_6_64 : + : : CLOUDLINUX_LATEST : + : : CLOUDLINUX_LATEST_64 : + : os (COREOS) : COREOS_CURRENT_64 : + : : COREOS_LATEST : + : : COREOS_LATEST_64 : + : os (DEBIAN) : DEBIAN_6_64 : + : : DEBIAN_LATEST_64 : + : os (OTHERUNIXLINUX) : OTHERUNIXLINUX_1_64 : + : : OTHERUNIXLINUX_LATEST : + : : OTHERUNIXLINUX_LATEST_64 : + : os (REDHAT) : REDHAT_5_64 : + : : REDHAT_6_64 : + : : REDHAT_7_64 : + : : REDHAT_LATEST : + : : REDHAT_LATEST_64 : + : san disk(0) : 25,100 : + : san disk(2) : 10,20,25,30,40,50,75,100,125,150,175,200,250,300,350,400,500,750,1000,1500,2000 : + : local disk(0) : 25,100 : + : local disk(2) : 25,100,150,200,300 : + : local (dedicated host) disk(0) : 25,100 : + : nic (dedicated host) : 100,1000 : + :................................:.................................................................................: Here's the command to create a 2-core virtual server with 1GiB memory, running @@ -81,8 +82,8 @@ datacenter using the command `slcli vs create`. :: - $ slcli vs create --hostname=example --domain=softlayer.com -f B1_1X2X25 -o DEBIAN_LATEST_64 --datacenter=ams01 --billing=hourly - This action will incur charges on your account. Continue? [y/N]: y + $ slcli vs create --hostname=example --domain=softlayer.com -f B1_1X2X25 -o DEBIAN_LATEST_64 --datacenter=ams01 --billing=hourly + This action will incur charges on your account. Continue? [y/N]: y :..........:.................................:......................................:...........................: : ID : FQDN : guid : Order Date : :..........:.................................:......................................:...........................: @@ -115,20 +116,20 @@ instantly appear in your virtual server list now. :: - $ slcli vs list - :.........:............:.......................:.......:........:................:..............:....................: - : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : - :.........:............:.......................:.......:........:................:..............:....................: - : 1234567 : ams01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : - :.........:............:.......................:.......:........:................:..............:....................: + $ slcli vs list + :.........:............:.......................:.......:........:................:..............:....................: + : id : datacenter : host : cores : memory : primary_ip : backend_ip : active_transaction : + :.........:............:.......................:.......:........:................:..............:....................: + : 1234567 : ams01 : example.softlayer.com : 2 : 1G : 108.168.200.11 : 10.54.80.200 : Assign Host : + :.........:............:.......................:.......:........:................:..............:....................: Cool. You may ask, "It's creating... but how do I know when it's done?" Well, here's how: :: - $ slcli vs ready 'example' --wait=600 - READY + $ slcli vs ready 'example' --wait=600 + READY When the previous command returns, you'll know that the virtual server has finished the provisioning process and is ready to use. This is *very* useful @@ -140,34 +141,34 @@ username is 'root' and password is 'ABCDEFGH'. .. warning:: - Be careful when using the `--passwords` flag. This will print the virtual - server's password on the screen. Make sure no one is looking over your - shoulder. It's also advisable to change your root password soon after - creating your virtual server, or to create a user with sudo access and - disable SSH-based login directly to the root account. + Be careful when using the `--passwords` flag. This will print the virtual + server's password on the screen. Make sure no one is looking over your + shoulder. It's also advisable to change your root password soon after + creating your virtual server, or to create a user with sudo access and + disable SSH-based login directly to the root account. :: - $ slcli vs detail example --passwords - :..............:...........................: - : Name : Value : - :..............:...........................: - : id : 1234567 : - : hostname : example.softlayer.com : - : status : Active : - : state : Running : - : datacenter : ams01 : - : cores : 2 : - : memory : 1G : - : public_ip : 108.168.200.11 : - : private_ip : 10.54.80.200 : - : os : Debian : - : private_only : False : - : private_cpu : False : - : created : 2013-06-13T08:29:44-06:00 : - : modified : 2013-06-13T08:31:57-06:00 : - : users : root ABCDEFGH : - :..............:...........................: + $ slcli vs detail example --passwords + :..............:...........................: + : Name : Value : + :..............:...........................: + : id : 1234567 : + : hostname : example.softlayer.com : + : status : Active : + : state : Running : + : datacenter : ams01 : + : cores : 2 : + : memory : 1G : + : public_ip : 108.168.200.11 : + : private_ip : 10.54.80.200 : + : os : Debian : + : private_only : False : + : private_cpu : False : + : created : 2013-06-13T08:29:44-06:00 : + : modified : 2013-06-13T08:31:57-06:00 : + : users : root ABCDEFGH : + :..............:...........................: diff --git a/docs/cli_directory.rst b/docs/cli_directory.rst new file mode 100644 index 000000000..6be40cc90 --- /dev/null +++ b/docs/cli_directory.rst @@ -0,0 +1,10 @@ +.. _cli_directory: + +Command Directory +================= + +.. toctree:: + :maxdepth: 2 + :glob: + + cli/* diff --git a/docs/dev/cli.rst b/docs/dev/cli.rst index 31853174e..9545253aa 100644 --- a/docs/dev/cli.rst +++ b/docs/dev/cli.rst @@ -48,6 +48,7 @@ Then we need to register it so that `slcli table-example` will know to route to ... Which gives us + :: $ slcli table-example @@ -143,32 +144,46 @@ All commands should be documented, luckily there is a sphinx module that makes t If you were adding a summary command to `slcli account` you would find the documentation in `docs/cli/account.rst` and you would just need to add this for your command -``` -.. click:: SoftLayer.CLI.account.summary:cli - :prog: account summary - :show-nested: -``` +:: + + .. click:: SoftLayer.CLI.account.summary:cli + :prog: account summary + :show-nested: The following REGEX can take the route entry and turn it into a document entry. -``` -s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ -``` +:: + + s/^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$/.. click:: $3\n :prog: $1 $2\n :show-nested:\n/ + + +Find:: + + ^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ -FIND: -``` -^\('([a-z]*):([a-z-]*)', '([a-zA-Z\.:_]*)'\),$ -``` -REPLACE: -``` -.. click:: $3 - :prog: $1 $2 - :show-nested: +REPLACE:: -``` + .. click:: $3 + :prog: $1 $2 + :show-nested: I tried to get sphinx-click to auto document the ENTIRE slcli, but the results were all on one page, and required a few changes to sphinx-click itself to work. This is due to the fact that most commands in SLCLI use the function name "cli", and some hacks would have to be put inplace to use the path name instead. + + +Architecture +------------ + +*SLCLI* is the base command, and it starts at *SoftLayer\CLI\core.py*. Commands are loaded from the *SoftLayer\CLI\routes.py* file. How Click figures this out is defined by the *CommandLoader* class in core.py, which is an example of a `MultiCommand `_. + +There are a few examples of commands that are three levels deep, that use a bit more graceful command loader. + +- *SoftLayer\CLI\virt\capacity\__init__.py* +- *SoftLayer\CLI\virt\placementgroup\__init__.py* +- *SoftLayer\CLI\object_storage\credential\__init__.py* + +These commands are not directly listed in the routes file, because the autoloader doesn't have the ability to parse multiple commands like that. For now it was easier to make the rare thrid level commands have their own special loader than re-write the base command loader to be able to look deeper into the project for commands. + diff --git a/docs/index.rst b/docs/index.rst index 1b3c2b390..1d801d53d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ in order to manage SoftLayer services. config_file api/* cli + cli_directory Contributing From d4de69f5f2c1a9730ab7cde2177157bee82c423b Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 29 Jan 2020 16:05:52 -0600 Subject: [PATCH 0240/1582] v5.8.5 --- CHANGELOG.md | 20 +++++++++++++++++++- SoftLayer/consts.py | 2 +- docs/cli/block.rst | 13 +++++++++++++ setup.py | 2 +- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ff0afc50..444c459cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,25 @@ # Change Log -## [5.8.5] - 2019-12-20 +## [5.8.5] - 2012-01-29 +https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 + +- #1195 Fixed an issue with `slcli vs dns-sync --ptr`. Added `slcli hw dns-sync` +- #1199 Fix File Storage failback and failover. +- #1198 Fix issue where the summary command fails due to None being provided as the datacenter name. +- #1208 Added The following commands: + - `slcli block volume-limits` + - `slcli file volume-limits` +- #1209 Add testing/CI for python 3.8. +- #1212 Fix vs detail erroring on servers pending cancellation. +- #1210 support subnet ACL management through cli + + `slcli block subnets-list` + + `slcli block subnets-assign` + + `slcli block subnets-remove` +- #1215 Added documentation for all SLCLI commands. + + +## [5.8.4] - 2019-12-20 https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 - #1199 Fix block storage failback and failover. diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 89bcf5187..8bb1585fd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.4' +VERSION = 'v5.8.5' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 5ca8140b7..851d7d84c 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -110,3 +110,16 @@ Block Commands .. click:: SoftLayer.CLI.block.limit:cli :prog: block volume-limits :show-nested: + + +.. click:: SoftLayer.CLI.block.subnets.list:cli + :prog: block subnets-list + :show-nested: + +.. click:: SoftLayer.CLI.block.subnets.assign:cli + :prog: block subnets-assign + :show-nested: + +.. click:: SoftLayer.CLI.block.subnets.remove:cli + :prog: block subnets-remove + :show-nested: diff --git a/setup.py b/setup.py index 65b1d5c44..b88b324c0 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.4', + version='5.8.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From d205feed7d3022960dabbcb439b33e76cd39dc26 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 17:54:39 -0400 Subject: [PATCH 0241/1582] Added get lbaas by address method --- SoftLayer/managers/load_balancer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index ea67e469c..0e93114a8 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -100,7 +100,20 @@ def get_lbaas_uuid_id(self, identifier): this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") else: this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") - return this_lb['uuid'], this_lb['id'] + return this_lb.get('uuid'), this_lb.get('id') + + def get_lbaas_by_address(self, address): + """Gets a LBaaS by address. + + :param address: Address of the LBaaS instance + """ + this_lb = {} + this_lbs = self.lbaas.getAllObjects() + for lbaas in this_lbs: + if lbaas.get('address') == address: + this_lb = lbaas + break + return this_lb def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance From e05c70c6e6012ebdf25c42ea3a6aa5e55a1c61b3 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:00:57 -0400 Subject: [PATCH 0242/1582] Added load balancer detail by name --- SoftLayer/CLI/loadbal/detail.py | 12 ++++++++++-- SoftLayer/utils.py | 10 ++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index eb832d594..55d6a01f3 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -3,6 +3,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer import utils @@ -13,8 +14,15 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - _, lbid = mgr.get_lbaas_uuid_id(identifier) - this_lb = mgr.get_lb(lbid) + + if utils.valid_domain(identifier): + lbaas = mgr.get_lbaas_by_address(identifier) + if not lbaas: + raise exceptions.CLIAbort("{} address not found".format(identifier)) + this_lb = mgr.get_lb(lbaas.get('id')) + else: + _, lbid = mgr.get_lbaas_uuid_id(identifier) + this_lb = mgr.get_lb(lbid) if this_lb.get('previousErrorText'): print(this_lb.get('previousErrorText')) table = lbaas_table(this_lb) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 74ad84b96..99c37e8db 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -13,6 +13,16 @@ UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '!~', '*=', '^=', '$=', '_='] +DOMAIN_RE = re.compile(r'[-a-zA-Z0-9.]{1,40}\.') + + +def valid_domain(domain_name): + """Return whether or not given value is a valid domain. + + :param domain_name: domain string to validate. + + """ + return DOMAIN_RE.match(domain_name) def lookup(dic, key, *keys): From 768d14d5295c8cd1ac9231a07753ca8a8ff8df32 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:26:18 -0400 Subject: [PATCH 0243/1582] Added cli loadlal detail by address tests --- .../fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- tests/CLI/modules/loadbal_tests.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 1047a7ce8..4136ebb47 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,6 +1,6 @@ getObject = { 'accountId': 1234, - 'address': '01-307608-ams01.clb.appdomain.cloud', + 'address': 'test-01-307608-ams01.clb.appdomain.cloud ', 'createDate': '2019-08-12T07:49:43-06:00', 'id': 1111111, 'isPublic': 0, diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index b87dab6e9..4b011edbe 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -7,6 +7,7 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer.fixtures import SoftLayer_Product_Package @@ -216,6 +217,16 @@ def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + def test_lb_detail_by_address(self): + address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') + result = self.run_command(['lb', 'detail', address]) + self.assert_no_fail(result) + + def test_lb_detail_address_not_found(self): + address = 'test-01-ams01.clb.appdomain.cloud' + result = self.run_command(['lb', 'detail', address]) + self.assertIsInstance(result.exception, CLIAbort) + def test_order(self): result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', 'labeltest', '--subnet', '759282']) From 6066024266c6b069dfb542e8af6689f4ffaff683 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 18:53:07 -0400 Subject: [PATCH 0244/1582] Increased coverage for loadbal manager by adding test --- SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- tests/managers/loadbal_tests.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 4136ebb47..aacf8e61c 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -1,6 +1,6 @@ getObject = { 'accountId': 1234, - 'address': 'test-01-307608-ams01.clb.appdomain.cloud ', + 'address': 'test-01-307608-ams01.clb.appdomain.cloud', 'createDate': '2019-08-12T07:49:43-06:00', 'id': 1111111, 'isPublic': 0, diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index cf93ed9a6..673d295c9 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -9,6 +9,7 @@ """ import SoftLayer from SoftLayer import testing +from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer class LoadBalancerTests(testing.TestCase): @@ -176,3 +177,9 @@ def test_cancel_lbaas(self): uuid = 'aa-bb-cc' self.lb_mgr.cancel_lbaas(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) + + def test_get_lbaas_by_address(self): + address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') + load_bal = self.lb_mgr.get_lbaas_by_address(address) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertIsNotNone(load_bal) From 1b6f6a2814cd80480781e628678657a474455f88 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 Feb 2020 19:00:11 -0400 Subject: [PATCH 0245/1582] fixed tox issues --- tests/managers/loadbal_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 673d295c9..4ac34e620 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -8,8 +8,8 @@ them directly to the API. """ import SoftLayer +from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer import testing -from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer class LoadBalancerTests(testing.TestCase): @@ -156,7 +156,7 @@ def test_order_lbaas(self): 'description': desc, 'location': datacenter, 'packageId': package[0]['id'], - 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': package[0]['itemPrices'][0]['id']}], 'protocolConfigurations': protocols, 'subnets': [{'id': subnet_id}], From 52a71a352d32a5ffc95474f7ed901012ea100204 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 11 Feb 2020 15:46:16 -0400 Subject: [PATCH 0246/1582] add note about using multiple colon symbols --- docs/cli/hardware.rst | 1 + docs/cli/vs.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index c11402bd3..ea7910a06 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -43,6 +43,7 @@ Provides some basic functionality to order a server. `slcli order` has a more fu When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. +**Note :** Using multiple colon symbols can cause an error. .. click:: SoftLayer.CLI.hardware.list:cli :prog: hw list diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..374986797 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -203,6 +203,8 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs edit :show-nested: +**Note :** Using multiple colon symbols can cause an error. + .. click:: SoftLayer.CLI.virt.list:cli :prog: vs list :show-nested: From 5ebacfd09def8af7a228226780b5d0b5c9d6e660 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 11 Feb 2020 16:37:20 -0400 Subject: [PATCH 0247/1582] #1221 Added version checker --- SoftLayer/CLI/core.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 3a552df79..f962e8df8 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,12 +5,14 @@ :license: MIT, see LICENSE for more details. """ +import json import logging import os import sys import time import traceback import types +import urllib3 import click @@ -69,6 +71,26 @@ def get_command(self, ctx, name): return module +def get_latest_version(): + """Gets the latest version of the Softlayer library.""" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + http = urllib3.PoolManager() + try: + str_result = http.request('GET', 'https://pypi.python.org/pypi/softlayer/json') + dic_result = json.loads(str_result.data.decode('utf-8')) + latest = dic_result['info']['version'] + except Exception: + latest = '%(version)s' + return latest + + +def get_version_message(): + """Gets current and latest release versions message.""" + message = 'Current: %(prog)s %(version)s \n' + latest = get_latest_version() + return message + 'Latest: %(prog)s ' + latest + + @click.group(help="SoftLayer Command-line Client", epilog="""To use most commands your SoftLayer username and api_key need to be configured. The easiest way to do that is to @@ -103,7 +125,8 @@ def get_command(self, ctx, name): is_flag=True, required=False, help="Use demo data instead of actually making API calls") -@click.version_option(prog_name="slcli (SoftLayer Command-line)") +@click.version_option(prog_name="slcli (SoftLayer Command-line)", + message=get_version_message()) @environment.pass_env def cli(env, format='table', From 04015ae95dd399ab9ad710db4909db02f182bddd Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 12 Feb 2020 16:09:13 -0600 Subject: [PATCH 0248/1582] removed todo message --- SoftLayer/CLI/user/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/permissions.py b/SoftLayer/CLI/user/permissions.py index b1851acb2..bf77c41ae 100644 --- a/SoftLayer/CLI/user/permissions.py +++ b/SoftLayer/CLI/user/permissions.py @@ -11,7 +11,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """User Permissions. TODO change to list all permissions, and which users have them""" + """User Permissions.""" mgr = SoftLayer.UserManager(env.client) user_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'username') From e4e319e201a2ecca5fc0c664f603a193f226edca Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 12 Feb 2020 18:53:24 -0400 Subject: [PATCH 0249/1582] fix the Christopher comment code review --- docs/cli/hardware.rst | 11 +++++++++-- docs/cli/vs.rst | 2 -- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index ea7910a06..3cd899d4a 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -41,9 +41,16 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :prog: hw edit :show-nested: -When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. +**Note :** Using multiple ' **:** ' can cause an error. + + $ slcli hw edit 123456 --tag "cloud:service:db2whoc, cloud:svcplan:flex, cloud:svcenv:prod, cloud:bmixenv:fra" + + TransportError(0): ('Connection aborted.', -**Note :** Using multiple colon symbols can cause an error. + RemoteDisconnected('Remote end closed connection without response',)) + + +When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. .. click:: SoftLayer.CLI.hardware.list:cli :prog: hw list diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 374986797..afa0f8b2d 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -203,8 +203,6 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs edit :show-nested: -**Note :** Using multiple colon symbols can cause an error. - .. click:: SoftLayer.CLI.virt.list:cli :prog: vs list :show-nested: From 264c674c91026856e48daef9f0c2d19103f2b926 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 12 Feb 2020 19:38:55 -0400 Subject: [PATCH 0250/1582] #1221 changes requested and unit tests --- SoftLayer/CLI/core.py | 28 +++++++++++++++------------- tests/CLI/core_tests.py | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index f962e8df8..fe86f714e 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -5,17 +5,16 @@ :license: MIT, see LICENSE for more details. """ -import json import logging import os import sys import time import traceback import types -import urllib3 import click +import requests import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -33,6 +32,7 @@ 3: logging.DEBUG } +PROG_NAME = "slcli (SoftLayer Command-line)" VALID_FORMATS = ['table', 'raw', 'json', 'jsonraw'] DEFAULT_FORMAT = 'raw' if sys.stdout.isatty(): @@ -73,22 +73,24 @@ def get_command(self, ctx, name): def get_latest_version(): """Gets the latest version of the Softlayer library.""" - urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - http = urllib3.PoolManager() try: - str_result = http.request('GET', 'https://pypi.python.org/pypi/softlayer/json') - dic_result = json.loads(str_result.data.decode('utf-8')) - latest = dic_result['info']['version'] + result = requests.get('https://pypi.org/pypi/SoftLayer/json') + json_result = result.json() + latest = 'v{}'.format(json_result['info']['version']) except Exception: - latest = '%(version)s' + latest = "Unable to get version from pypi." return latest -def get_version_message(): +def get_version_message(ctx, param, value): """Gets current and latest release versions message.""" - message = 'Current: %(prog)s %(version)s \n' + if not value or ctx.resilient_parsing: + return + current = SoftLayer.consts.VERSION latest = get_latest_version() - return message + 'Latest: %(prog)s ' + latest + click.secho("Current: {prog} {current}\nLatest: {prog} {latest}".format( + prog=PROG_NAME, current=current, latest=latest)) + ctx.exit() @click.group(help="SoftLayer Command-line Client", @@ -125,8 +127,8 @@ def get_version_message(): is_flag=True, required=False, help="Use demo data instead of actually making API calls") -@click.version_option(prog_name="slcli (SoftLayer Command-line)", - message=get_version_message()) +@click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, + help="Show version information.") @environment.pass_env def cli(env, format='table', diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index 321f78b1e..f230a3513 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -10,6 +10,7 @@ import click import mock +from requests.models import Response import SoftLayer from SoftLayer.CLI import core from SoftLayer.CLI import environment @@ -52,6 +53,28 @@ def test_diagnostics(self): self.assertIn('"python_version"', result.output) self.assertIn('"library_location"', result.output) + @mock.patch('requests.get') + def test_get_latest_version(self, request_get): + response = Response() + response.status_code = 200 + response.json = mock.MagicMock(return_value={"info": {"version": "1.1.1"}}) + request_get.return_value = response + version = core.get_latest_version() + self.assertIn('1.1.1', version) + + @mock.patch('requests.get') + def test_unable_get_latest_version(self, request_get): + request_get.side_effect = Exception + version = core.get_latest_version() + self.assertIn('Unable', version) + + @mock.patch('SoftLayer.CLI.core.get_latest_version') + def test_get_version_message(self, get_latest_version_mock): + get_latest_version_mock.return_value = '1.1.1' + env = environment.Environment() + result = self.run_command(['--version'], env=env) + self.assert_no_fail(result) + class CoreMainTests(testing.TestCase): From 332f30023e7cd2cc30af3efad060f3cb43976517 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 13 Feb 2020 16:09:28 -0400 Subject: [PATCH 0251/1582] #1222 changing slcli lb list to display the name instead of the address --- SoftLayer/CLI/loadbal/list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/loadbal/list.py b/SoftLayer/CLI/loadbal/list.py index 4eb2f5731..02ce5bb7c 100644 --- a/SoftLayer/CLI/loadbal/list.py +++ b/SoftLayer/CLI/loadbal/list.py @@ -30,17 +30,17 @@ def location_sort(location): def generate_lbaas_table(lbaas): """Takes a list of SoftLayer_Network_LBaaS_LoadBalancer and makes a table""" table = formatting.Table([ - 'Id', 'Location', 'Address', 'Description', 'Public', 'Create Date', 'Members', 'Listeners' + 'Id', 'Location', 'Name', 'Description', 'Public', 'Create Date', 'Members', 'Listeners' ], title="IBM Cloud LoadBalancer") - table.align['Address'] = 'l' + table.align['Name'] = 'l' table.align['Description'] = 'l' table.align['Location'] = 'l' for this_lb in sorted(lbaas, key=location_sort): table.add_row([ this_lb.get('id'), utils.lookup(this_lb, 'datacenter', 'longName'), - this_lb.get('address'), + this_lb.get('name'), this_lb.get('description'), 'Yes' if this_lb.get('isPublic', 1) == 1 else 'No', utils.clean_time(this_lb.get('createDate')), From 5a2a73677bcd36fb130e8e74375f6996744d6d13 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Thu, 13 Feb 2020 16:14:09 -0600 Subject: [PATCH 0252/1582] updated tavis.yml for Ubuntu bionic and pypy3.5 -> pypy3. specifically using 3.5 seems to be broken on travis servers, so the latest 3.6 should be fine --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e40cb2a51..35c987a63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ # https://docs.travis-ci.com/user/languages/python/#python-37-and-higher -dist: xenial +dist: bionic language: python sudo: false matrix: @@ -12,7 +12,7 @@ matrix: env: TOX_ENV=py37 - python: "3.8" env: TOX_ENV=py38 - - python: "pypy3.5" + - python: "pypy3" env: TOX_ENV=pypy3 - python: "3.6" env: TOX_ENV=analysis From ee748de19551cbbffb30e3d88ea47b09566fa4e8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 14 Feb 2020 12:36:46 -0400 Subject: [PATCH 0253/1582] #1222 Added logic to lookup LBaaS by name instead of the address --- SoftLayer/CLI/loadbal/detail.py | 12 ++----- .../SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- SoftLayer/managers/load_balancer.py | 34 ++++++++++--------- SoftLayer/utils.py | 12 +------ tests/CLI/modules/loadbal_tests.py | 15 ++++---- tests/managers/loadbal_tests.py | 15 ++++++-- 6 files changed, 41 insertions(+), 49 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 55d6a01f3..eb832d594 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -3,7 +3,6 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer import utils @@ -14,15 +13,8 @@ def cli(env, identifier): """Get Load Balancer as a Service details.""" mgr = SoftLayer.LoadBalancerManager(env.client) - - if utils.valid_domain(identifier): - lbaas = mgr.get_lbaas_by_address(identifier) - if not lbaas: - raise exceptions.CLIAbort("{} address not found".format(identifier)) - this_lb = mgr.get_lb(lbaas.get('id')) - else: - _, lbid = mgr.get_lbaas_uuid_id(identifier) - this_lb = mgr.get_lb(lbid) + _, lbid = mgr.get_lbaas_uuid_id(identifier) + this_lb = mgr.get_lb(lbid) if this_lb.get('previousErrorText'): print(this_lb.get('previousErrorText')) table = lbaas_table(this_lb) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index aacf8e61c..94220cdea 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -6,7 +6,7 @@ 'isPublic': 0, 'locationId': 265592, 'modifyDate': '2019-08-13T16:26:06-06:00', - 'name': 'dcabero-01', + 'name': 'test-01', 'operatingStatus': 'ONLINE', 'provisioningStatus': 'ACTIVE', 'type': 0, diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 0e93114a8..62d2a3075 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -92,28 +92,30 @@ def update_lb_health_monitors(self, uuid, checks): def get_lbaas_uuid_id(self, identifier): """Gets a LBaaS uuid, id. Since sometimes you need one or the other. - :param identifier: either the LB Id, or UUID, this function will return both. + :param identifier: either the LB Id, UUID or Name, this function will return UUI and LB Id. :return (uuid, id): """ - # int objects don't have a len property. - if not isinstance(identifier, int) and len(identifier) == 36: - this_lb = self.lbaas.getLoadBalancer(identifier, mask="mask[id,uuid]") + mask = "mask[id,uuid]" + if isinstance(identifier, int): + this_lb = self.lbaas.getObject(id=identifier, mask=mask) + elif len(identifier) == 36 and utils.UUID_RE.match(identifier): + this_lb = self.lbaas.getLoadBalancer(identifier, mask=mask) else: - this_lb = self.lbaas.getObject(id=identifier, mask="mask[id,uuid]") + this_lb = self.get_lbaas_by_name(identifier, mask=mask) + return this_lb.get('uuid'), this_lb.get('id') - def get_lbaas_by_address(self, address): - """Gets a LBaaS by address. + def get_lbaas_by_name(self, name, mask=None): + """Gets a LBaaS by name. - :param address: Address of the LBaaS instance + :param name: Name of the LBaaS instance + :param mask: + :returns: SoftLayer_Network_LBaaS_LoadBalancer or an empty dictionary if the name is not found. """ - this_lb = {} - this_lbs = self.lbaas.getAllObjects() - for lbaas in this_lbs: - if lbaas.get('address') == address: - this_lb = lbaas - break - return this_lb + object_filter = {'name': {'operation': name}} + this_lbs = self.lbaas.getAllObjects(filter=object_filter, mask=mask) + + return this_lbs[0] if this_lbs else {} def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance @@ -210,7 +212,7 @@ def order_lbaas(self, datacenter, name, desc, protocols, subnet_id, public=False 'description': desc, 'location': datacenter, 'packageId': package.get('id'), - 'useHourlyPricing': True, # Required since LBaaS is an hourly service + 'useHourlyPricing': True, # Required since LBaaS is an hourly service 'prices': [{'id': price_id} for price_id in prices], 'protocolConfigurations': protocols, 'subnets': [{'id': subnet_id}], diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 99c37e8db..0234bf72d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -11,18 +11,8 @@ # pylint: disable=no-member, invalid-name -UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) +UUID_RE = re.compile(r'^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$', re.I) KNOWN_OPERATIONS = ['<=', '>=', '<', '>', '~', '!~', '*=', '^=', '$=', '_='] -DOMAIN_RE = re.compile(r'[-a-zA-Z0-9.]{1,40}\.') - - -def valid_domain(domain_name): - """Return whether or not given value is a valid domain. - - :param domain_name: domain string to validate. - - """ - return DOMAIN_RE.match(domain_name) def lookup(dic, key, *keys): diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 4b011edbe..84866d3f5 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -7,7 +7,6 @@ import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError -from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer.fixtures import SoftLayer_Product_Package @@ -217,15 +216,15 @@ def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) - def test_lb_detail_by_address(self): - address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') - result = self.run_command(['lb', 'detail', address]) + def test_lb_detail_by_name(self): + name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') + result = self.run_command(['lb', 'detail', name]) self.assert_no_fail(result) - def test_lb_detail_address_not_found(self): - address = 'test-01-ams01.clb.appdomain.cloud' - result = self.run_command(['lb', 'detail', address]) - self.assertIsInstance(result.exception, CLIAbort) + def test_lb_detail_uuid(self): + uuid = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('uuid') + result = self.run_command(['lb', 'detail', uuid]) + self.assert_no_fail(result) def test_order(self): result = self.run_command(['loadbal', 'order', '--name', 'test', '--datacenter', 'par01', '--label', diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 4ac34e620..38031204d 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -79,6 +79,15 @@ def test_get_lbaas_uuid_id_id(self): self.assertEqual(lb_uuid, uuid) self.assertEqual(lb_id, my_id) + def test_get_lbaas_uuid_id_name(self): + uuid = '1a1aa111-4474-4e16-9f02-4de959229b85' + my_id = 1111111 + name = 'test-01' + lb_uuid, lb_id = self.lb_mgr.get_lbaas_uuid_id(name) + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertEqual(lb_uuid, uuid) + self.assertEqual(lb_id, my_id) + def test_delete_lb_member(self): uuid = 'aa-bb-cc' member_id = 'dd-ee-ff' @@ -178,8 +187,8 @@ def test_cancel_lbaas(self): self.lb_mgr.cancel_lbaas(uuid) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'cancelLoadBalancer', args=(uuid,)) - def test_get_lbaas_by_address(self): - address = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('address') - load_bal = self.lb_mgr.get_lbaas_by_address(address) + def test_get_lbaas_by_name(self): + name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') + load_bal = self.lb_mgr.get_lbaas_by_name(name) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') self.assertIsNotNone(load_bal) From f4756a04e7d54e3813c738954130731ef40ffc39 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 15:37:26 -0600 Subject: [PATCH 0254/1582] add dep. dupe ordering support --- SoftLayer/CLI/block/duplicate.py | 11 +++++++++-- SoftLayer/CLI/file/duplicate.py | 12 ++++++++++-- SoftLayer/managers/block.py | 8 +++++++- SoftLayer/managers/file.py | 8 +++++++- SoftLayer/managers/storage_utils.py | 8 +++++++- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index ec728f87c..83b8bd9bc 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -54,9 +54,15 @@ type=click.Choice(['hourly', 'monthly']), default='monthly', help="Optional parameter for Billing rate (default to monthly)") +@click.option('--dependent-duplicate', + type=click.BOOL, + default=False, + help='Whether or not this duplicate will be a dependent duplicate ' + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, + dependent_duplicate): """Order a duplicate block storage volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) @@ -75,7 +81,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, duplicate_snapshot_size=duplicate_snapshot_size, - hourly_billing_flag=hourly_billing_flag + hourly_billing_flag=hourly_billing_flag, + dependent_duplicate=dependent_duplicate ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index a3b4c801a..b722a564d 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -52,9 +52,16 @@ type=click.Choice(['hourly', 'monthly']), default='monthly', help="Optional parameter for Billing rate (default to monthly)") +@click.option('--dependent-duplicate', + type=click.BOOL, + default=False, + help='Whether or not this duplicate will be a dependent duplicate' + 'of the origin volume (default to false)') + @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, - duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing): + duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, + dependent_duplicate): """Order a duplicate file storage volume.""" file_manager = SoftLayer.FileStorageManager(env.client) @@ -73,7 +80,8 @@ def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops=duplicate_iops, duplicate_tier_level=duplicate_tier, duplicate_snapshot_size=duplicate_snapshot_size, - hourly_billing_flag=hourly_billing_flag + hourly_billing_flag=hourly_billing_flag, + dependent_duplicate=dependent_duplicate ) except ValueError as ex: raise exceptions.ArgumentError(str(ex)) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 11b998935..90bceeb70 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -310,7 +310,8 @@ 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): + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate block volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -321,6 +322,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ @@ -348,6 +351,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + order['isDependentDuplicateFlag'] = 1 + 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): diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 95cf85c88..ef8a7c758 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -259,7 +259,8 @@ 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): + hourly_billing_flag=False, + dependent_duplicate=False): """Places an order for a duplicate file volume. :param origin_volume_id: The ID of the origin volume to be duplicated @@ -270,6 +271,8 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ @@ -289,6 +292,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + order['isDependentDuplicateFlag'] = 1 + 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): diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 80ec60368..d3d377a11 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -788,7 +788,8 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, def prepare_duplicate_order_object(manager, origin_volume, iops, tier, duplicate_size, duplicate_snapshot_size, - volume_type, hourly_billing_flag=False): + volume_type, hourly_billing_flag=False, + dependent_duplicate=False): """Prepare the duplicate order to submit to SoftLayer_Product::placeOrder() :param manager: The File or Block manager calling this function @@ -799,6 +800,8 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, :param duplicate_snapshot_size: The size for the duplicate snapshot space :param volume_type: The type of the origin volume ('file' or 'block') :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent + duplicate (True) :return: Returns the order object to be passed to the placeOrder() method of the Product_Order service """ @@ -903,6 +906,9 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, if volume_is_performance: duplicate_order['iops'] = iops + if dependent_duplicate: + duplicate_order['isDependentDuplicateFlag'] = 1 + return duplicate_order From 3be7a9d1375b578ce734bb6254258f3f5bf6a6c4 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 15:37:58 -0600 Subject: [PATCH 0255/1582] add myself to CONTRIBUTORS --- CONTRIBUTORS | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4de814b58..5b475dc73 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -6,6 +6,7 @@ chechuironman Christopher Gallo David Ibarra Hans Kristian Moen +Ian Sutton Jake Williams Jason Johnson Kevin Landreth From faa6cd5b4508ef838eacac466fac0315a1445a9f Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:06:04 -0600 Subject: [PATCH 0256/1582] fix tests --- tests/CLI/modules/block_tests.py | 3 ++- tests/CLI/modules/file_tests.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f42a864ef..e59288981 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -642,7 +642,8 @@ def test_duplicate_order_hourly_billing(self, order_mock): duplicate_size=250, duplicate_iops=None, duplicate_tier_level=2, duplicate_snapshot_size=20, - hourly_billing_flag=True) + hourly_billing_flag=True, + dependent_duplicate=False) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #24602 placed successfully!\n' diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 0fc4ffc06..e1d8628cb 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -629,7 +629,8 @@ def test_duplicate_order_hourly_billing(self, order_mock): duplicate_size=250, duplicate_iops=None, duplicate_tier_level=2, duplicate_snapshot_size=20, - hourly_billing_flag=True) + hourly_billing_flag=True, + dependent_duplicate=False) self.assert_no_fail(result) self.assertEqual(result.output, 'Order #24602 placed successfully!\n' From 2c5026c3fead6e2c9e24c023e49c31b83db765e9 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:29:24 -0600 Subject: [PATCH 0257/1582] more tests and analysis checker fixes --- SoftLayer/CLI/block/duplicate.py | 2 +- SoftLayer/CLI/file/duplicate.py | 3 +- SoftLayer/managers/storage_utils.py | 2 +- tests/managers/block_tests.py | 47 +++++++++++++++++++++++++++++ tests/managers/file_tests.py | 45 +++++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index 83b8bd9bc..df041bff9 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -58,7 +58,7 @@ type=click.BOOL, default=False, help='Whether or not this duplicate will be a dependent duplicate ' - 'of the origin volume (default to false)') + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index b722a564d..ad14faf70 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -56,8 +56,7 @@ type=click.BOOL, default=False, help='Whether or not this duplicate will be a dependent duplicate' - 'of the origin volume (default to false)') - + 'of the origin volume (default to false)') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index d3d377a11..b49e0eb45 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -800,7 +800,7 @@ def prepare_duplicate_order_object(manager, origin_volume, iops, tier, :param duplicate_snapshot_size: The size for the duplicate snapshot space :param volume_type: The type of the origin volume ('file' or 'block') :param hourly_billing_flag: Billing type, monthly (False) or hourly (True) - :param dependent_duplicate: Duplicate type, normal (False) or dependent + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns the order object to be passed to the placeOrder() method of the Product_Order service diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index acb0dc6f6..9a81ca374 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -846,6 +846,53 @@ def test_order_block_duplicate_performance(self): 'useHourlyPricing': False },)) + def test_order_block_duplicate_depdupe(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.block.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=2000, + duplicate_tier_level=None, + duplicate_snapshot_size=10, + dependent_duplicate=True + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189443}, + {'id': 190113}, + {'id': 190173}, + {'id': 191193} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'osFormatType': {'keyName': 'LINUX'}, + 'duplicateOriginSnapshotId': 470, + 'iops': 2000, + 'useHourlyPricing': False, + 'isDependentDuplicateFlag': 1 + },)) + + def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index d6ca66c68..9a8224b3a 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -742,6 +742,51 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 'useHourlyPricing': False },)) + def test_order_file_duplicate_depdupe(self): + mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' + mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') + mock.return_value = mock_volume + + result = self.file.order_duplicate_volume( + 102, + origin_snapshot_id=470, + duplicate_size=1000, + duplicate_iops=None, + duplicate_tier_level=4, + duplicate_snapshot_size=10, + dependent_duplicate=True + ) + + self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + + self.assert_called_with( + 'SoftLayer_Product_Order', + 'placeOrder', + args=({ + 'complexType': 'SoftLayer_Container_Product_Order_' + 'Network_Storage_AsAService', + 'packageId': 759, + 'prices': [ + {'id': 189433}, + {'id': 189453}, + {'id': 194763}, + {'id': 194703}, + {'id': 194943} + ], + 'volumeSize': 1000, + 'quantity': 1, + 'location': 449500, + 'duplicateOriginVolumeId': 102, + 'duplicateOriginSnapshotId': 470, + 'useHourlyPricing': False, + 'isDependentDuplicateFlag': 1 + },)) + + def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] From 8b6bdfe618157d37451ab5c247a0e3963edaac98 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:33:20 -0600 Subject: [PATCH 0258/1582] fix test analysis checks --- tests/managers/block_tests.py | 1 - tests/managers/file_tests.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 9a81ca374..e78622105 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -892,7 +892,6 @@ def test_order_block_duplicate_depdupe(self): 'isDependentDuplicateFlag': 1 },)) - def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 9a8224b3a..69c9c60ed 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -786,7 +786,6 @@ def test_order_file_duplicate_depdupe(self): 'isDependentDuplicateFlag': 1 },)) - def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] From e424ce15eaf24d327800d13e4003578b7e392577 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:52:49 -0600 Subject: [PATCH 0259/1582] fix minor changes requested --- SoftLayer/CLI/block/duplicate.py | 3 ++- SoftLayer/CLI/file/duplicate.py | 3 ++- SoftLayer/managers/file.py | 3 +-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/duplicate.py b/SoftLayer/CLI/block/duplicate.py index df041bff9..ff9ae961a 100644 --- a/SoftLayer/CLI/block/duplicate.py +++ b/SoftLayer/CLI/block/duplicate.py @@ -57,8 +57,9 @@ @click.option('--dependent-duplicate', type=click.BOOL, default=False, + show_default=True, help='Whether or not this duplicate will be a dependent duplicate ' - 'of the origin volume (default to false)') + 'of the origin volume.') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/CLI/file/duplicate.py b/SoftLayer/CLI/file/duplicate.py index ad14faf70..13e3dcfc5 100644 --- a/SoftLayer/CLI/file/duplicate.py +++ b/SoftLayer/CLI/file/duplicate.py @@ -55,8 +55,9 @@ @click.option('--dependent-duplicate', type=click.BOOL, default=False, + show_default=True, help='Whether or not this duplicate will be a dependent duplicate' - 'of the origin volume (default to false)') + 'of the origin volume.') @environment.pass_env def cli(env, origin_volume_id, origin_snapshot_id, duplicate_size, duplicate_iops, duplicate_tier, duplicate_snapshot_size, billing, diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index ef8a7c758..0a5214e67 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -271,8 +271,7 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. - :param dependent_duplicate: Duplicate type, normal (False) or dependent - duplicate (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ From 7a651b05d6d96080846e4576734ebd69eeef6dd5 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Mon, 17 Feb 2020 16:54:16 -0600 Subject: [PATCH 0260/1582] minor line length issue --- SoftLayer/managers/block.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 90bceeb70..25e6839f2 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -322,8 +322,7 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, :param duplicate_snapshot_size: Snapshot space size for the duplicate :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. - :param dependent_duplicate: Duplicate type, normal (False) or dependent - duplicate (True) + :param dependent_duplicate: Duplicate type, normal (False) or dependent duplicate (True) :return: Returns a SoftLayer_Container_Product_Order_Receipt """ From c1477f86654a241dac6ef4502b3e24a15764af11 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 17 Feb 2020 19:08:52 -0400 Subject: [PATCH 0261/1582] #1222 check if the identifier is really just a number --- SoftLayer/managers/load_balancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 62d2a3075..72b13ab46 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -96,7 +96,7 @@ def get_lbaas_uuid_id(self, identifier): :return (uuid, id): """ mask = "mask[id,uuid]" - if isinstance(identifier, int): + if isinstance(identifier, int) or identifier.isdigit(): this_lb = self.lbaas.getObject(id=identifier, mask=mask) elif len(identifier) == 36 and utils.UUID_RE.match(identifier): this_lb = self.lbaas.getLoadBalancer(identifier, mask=mask) From fd7b0cf2fa0e51ae22270ac9987ca2813ce01d26 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 17 Feb 2020 19:48:42 -0400 Subject: [PATCH 0262/1582] #1222 raise an exception if not found find a lbaas instance --- SoftLayer/managers/load_balancer.py | 7 +++++-- tests/managers/loadbal_tests.py | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 72b13ab46..3ffa09594 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer.managers import ordering from SoftLayer import utils @@ -110,12 +111,14 @@ def get_lbaas_by_name(self, name, mask=None): :param name: Name of the LBaaS instance :param mask: - :returns: SoftLayer_Network_LBaaS_LoadBalancer or an empty dictionary if the name is not found. + :returns: SoftLayer_Network_LBaaS_LoadBalancer. """ object_filter = {'name': {'operation': name}} this_lbs = self.lbaas.getAllObjects(filter=object_filter, mask=mask) + if not this_lbs: + raise exceptions.SoftLayerError("Unable to find LBaaS with name: {}".format(name)) - return this_lbs[0] if this_lbs else {} + return this_lbs[0] def delete_lb_member(self, identifier, member_id): """Removes a member from a LBaaS instance diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index 38031204d..d8058edcc 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -8,6 +8,7 @@ them directly to the API. """ import SoftLayer +from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Network_LBaaS_LoadBalancer from SoftLayer import testing @@ -192,3 +193,9 @@ def test_get_lbaas_by_name(self): load_bal = self.lb_mgr.get_lbaas_by_name(name) self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') self.assertIsNotNone(load_bal) + + def test_get_lbaas_by_name_fails(self): + load_bal_mock = self.set_mock('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + load_bal_mock.return_value = [] + name = 'test' + self.assertRaises(exceptions.SoftLayerError, self.lb_mgr.get_lbaas_by_name, name) From 109ea5ae3fa3438e3da443b7ae86f915e610685d Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Thu, 20 Feb 2020 15:10:47 -0600 Subject: [PATCH 0263/1582] bring in convert/refresh funcs --- SoftLayer/CLI/block/convert.py | 17 +++++++++++++++++ SoftLayer/CLI/block/refresh.py | 18 ++++++++++++++++++ SoftLayer/CLI/file/convert.py | 17 +++++++++++++++++ SoftLayer/CLI/file/refresh.py | 18 ++++++++++++++++++ SoftLayer/CLI/routes.py | 4 ++++ SoftLayer/managers/block.py | 17 +++++++++++++++++ SoftLayer/managers/file.py | 17 +++++++++++++++++ docs/cli/block.rst | 7 +++++++ 8 files changed, 115 insertions(+) create mode 100644 SoftLayer/CLI/block/convert.py create mode 100644 SoftLayer/CLI/block/refresh.py create mode 100644 SoftLayer/CLI/file/convert.py create mode 100644 SoftLayer/CLI/file/refresh.py diff --git a/SoftLayer/CLI/block/convert.py b/SoftLayer/CLI/block/convert.py new file mode 100644 index 000000000..795d3d27c --- /dev/null +++ b/SoftLayer/CLI/block/convert.py @@ -0,0 +1,17 @@ +"""Convert a dependent duplicate volume to an indepdent volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Convert a dependent duplicate volume to an indepdent volume.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + resp = block_manager.convert_dep_dupe(volume_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py new file mode 100644 index 000000000..66a2f3c22 --- /dev/null +++ b/SoftLayer/CLI/block/refresh.py @@ -0,0 +1,18 @@ +"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@click.argument('snapshot_id') +@environment.pass_env +def cli(env, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + resp = block_manager.refresh_dep_dupe(volume_id, snapshot_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/file/convert.py b/SoftLayer/CLI/file/convert.py new file mode 100644 index 000000000..7c01d8c53 --- /dev/null +++ b/SoftLayer/CLI/file/convert.py @@ -0,0 +1,17 @@ +"""Convert a dependent duplicate volume to an indepdent volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@environment.pass_env +def cli(env, volume_id): + """Convert a dependent duplicate volume to an indepdent volume.""" + file_manager = SoftLayer.FileStorageManager(env.client) + resp = file_manager.convert_dep_dupe(volume_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py new file mode 100644 index 000000000..c566dac91 --- /dev/null +++ b/SoftLayer/CLI/file/refresh.py @@ -0,0 +1,18 @@ +"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('volume_id') +@click.argument('snapshot_id') +@environment.pass_env +def cli(env, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + file_manager = SoftLayer.FileStorageManager(env.client) + resp = file_manager.refresh_dep_dupe(volume_id, snapshot_id) + + click.echo(resp) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 27bfb0b23..b908be4f5 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -106,6 +106,8 @@ ('block:volume-order', 'SoftLayer.CLI.block.order:cli'), ('block:volume-set-lun-id', 'SoftLayer.CLI.block.lun:cli'), ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), + ('block:volume-refresh', 'SoftLayer.CLI.block.refresh:cli'), + ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -137,6 +139,8 @@ ('file:volume-modify', 'SoftLayer.CLI.file.modify:cli'), ('file:volume-order', 'SoftLayer.CLI.file.order:cli'), ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), + ('file:volume-refresh', 'SoftLayer.CLI.file.refresh:cli'), + ('file:volume-convert', 'SoftLayer.CLI.file.convert:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 25e6839f2..8b379513f 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -616,3 +616,20 @@ def create_or_update_lun_id(self, volume_id, lun_id): """ return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', + snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', + id=volume_id) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 0a5214e67..2876daf98 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -527,3 +527,20 @@ def failback_from_replicant(self, volume_id): """ return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', + snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', + id=volume_id) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 851d7d84c..860872ce7 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -111,6 +111,13 @@ Block Commands :prog: block volume-limits :show-nested: +.. click:: SoftLayer.CLI.block.refresh:cli + :prog block volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.block.convert:cli + :prog block volume-convert + :show-nested: .. click:: SoftLayer.CLI.block.subnets.list:cli :prog: block subnets-list From 4007d758b43dbf7c1562200bd69210c9a690e236 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 20 Feb 2020 18:02:41 -0400 Subject: [PATCH 0264/1582] Fix the block and file storage detail. --- SoftLayer/CLI/block/detail.py | 15 ++++++++++- SoftLayer/CLI/file/detail.py | 15 ++++++++++- tests/CLI/modules/block_tests.py | 43 ++++++++++++++++++++++++++++++++ tests/CLI/modules/file_tests.py | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 6b1b7288c..73e93298f 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -5,16 +5,29 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer import utils +def get_block_volume_id(volume_id, block_manager): + storage_list = block_manager.list_block_volumes() + for storage in storage_list: + if volume_id == storage['username']: + volume_id = storage['id'] + break + + return volume_id + + @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) - block_volume = block_manager.get_block_volume_details(volume_id) + volume_id = get_block_volume_id(volume_id, block_manager) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') + block_volume = block_manager.get_block_volume_details(block_volume_id) block_volume = utils.NestedDict(block_volume) table = formatting.KeyValueTable(['Name', 'Value']) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 02bb5c17a..25af366f2 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -5,16 +5,29 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer import utils +def get_file_volume_id(volume_id, file_manager): + storage_list = file_manager.list_file_volumes() + for storage in storage_list: + if volume_id == storage['username']: + volume_id = storage['id'] + break + + return volume_id + + @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" file_manager = SoftLayer.FileStorageManager(env.client) - file_volume = file_manager.get_file_volume_details(volume_id) + volume_id = get_file_volume_id(volume_id, file_manager) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') + file_volume = file_manager.get_file_volume_details(file_volume_id) file_volume = utils.NestedDict(file_volume) table = formatting.KeyValueTable(['Name', 'Value']) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e59288981..42e574c48 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -98,6 +98,49 @@ def test_volume_detail(self): ] }, json.loads(result.output)) + def test_volume_detail_name_identifier(self): + result = self.run_command(['block', 'volume-detail', 'username']) + + self.assert_no_fail(result) + isinstance(json.loads(result.output)['IOPs'], float) + self.assertEqual({ + 'Username': 'username', + 'LUN Id': '2', + 'Endurance Tier': 'READHEAVY_TIER', + 'IOPs': 1000, + 'Snapshot Capacity (GB)': '10', + 'Snapshot Used (Bytes)': 1024, + 'Capacity (GB)': '20GB', + 'Target IP': '10.1.2.3', + 'Data Center': 'dal05', + 'Type': 'ENDURANCE', + 'ID': 100, + '# of Active Transactions': '1', + 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', + 'Replicant Count': '1', + 'Replication Status': 'Replicant Volume Provisioning ' + 'has completed.', + 'Replicant Volumes': [[ + {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, + {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, + {'Replicant ID': 'Data Center', '1784': 'wdc01'}, + {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, + ], [ + {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, + {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, + {'Replicant ID': 'Data Center', '1785': 'dal01'}, + {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, + ]], + 'Original Volume Properties': [ + {'Property': 'Original Volume Size', + 'Value': '20'}, + {'Property': 'Original Volume Name', + 'Value': 'test-original-volume-name'}, + {'Property': 'Original Snapshot Name', + 'Value': 'test-original-snapshot-name'} + ] + }, json.loads(result.output)) + def test_volume_list(self): result = self.run_command(['block', 'volume-list']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index e1d8628cb..e5e3ca556 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -163,6 +163,49 @@ def test_volume_detail(self): ] }, json.loads(result.output)) + def test_volume_detail_name_identifier(self): + result = self.run_command(['file', 'volume-detail', 'user']) + + self.assert_no_fail(result) + self.assertEqual({ + 'Username': 'username', + 'Used Space': '0B', + 'Endurance Tier': 'READHEAVY_TIER', + 'IOPs': 1000, + 'Mount Address': '127.0.0.1:/TEST', + 'Snapshot Capacity (GB)': '10', + 'Snapshot Used (Bytes)': 1024, + 'Capacity (GB)': '20GB', + 'Target IP': '10.1.2.3', + 'Data Center': 'dal05', + 'Type': 'ENDURANCE', + 'ID': 100, + '# of Active Transactions': '1', + 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', + 'Replicant Count': '1', + 'Replication Status': 'Replicant Volume Provisioning ' + 'has completed.', + 'Replicant Volumes': [[ + {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, + {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, + {'Replicant ID': 'Data Center', '1784': 'wdc01'}, + {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, + ], [ + {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, + {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, + {'Replicant ID': 'Data Center', '1785': 'dal01'}, + {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, + ]], + 'Original Volume Properties': [ + {'Property': 'Original Volume Size', + 'Value': '20'}, + {'Property': 'Original Volume Name', + 'Value': 'test-original-volume-name'}, + {'Property': 'Original Snapshot Name', + 'Value': 'test-original-snapshot-name'} + ] + }, json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', '--storage-type=performance', '--size=20', From b07e0ecd2ebb109ef3babb3df4b19edfb6706a3c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 20 Feb 2020 18:18:33 -0400 Subject: [PATCH 0265/1582] Add docstring for block and file new method. --- SoftLayer/CLI/block/detail.py | 6 ++++++ SoftLayer/CLI/file/detail.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 73e93298f..02ce0c82f 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -10,6 +10,12 @@ def get_block_volume_id(volume_id, block_manager): + """Returns the volume id. + + :param volume_id: ID of volume. + :param block_manager: Block Storage Manager. + :return: Returns the volume id. + """ storage_list = block_manager.list_block_volumes() for storage in storage_list: if volume_id == storage['username']: diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index 25af366f2..ad0393916 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -10,6 +10,12 @@ def get_file_volume_id(volume_id, file_manager): + """Returns the volume id. + + :param volume_id: ID of volume. + :param block_manager: Block Storage Manager. + :return: Returns the volume id. + """ storage_list = file_manager.list_file_volumes() for storage in storage_list: if volume_id == storage['username']: From dab368eb153110bc0eace325f4a2c701d5fd02a2 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:01:42 -0600 Subject: [PATCH 0266/1582] #1233 refactored the file/block managers to reduce duplicated code --- .../fixtures/SoftLayer_Location_Datacenter.py | 12 + SoftLayer/managers/block.py | 451 +----------------- SoftLayer/managers/file.py | 381 +-------------- SoftLayer/managers/storage.py | 415 ++++++++++++++++ SoftLayer/managers/storage_utils.py | 17 +- tests/managers/block_tests.py | 125 ++--- tests/managers/file_tests.py | 106 ++-- 7 files changed, 585 insertions(+), 922 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Location_Datacenter.py create mode 100644 SoftLayer/managers/storage.py diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py new file mode 100644 index 000000000..510fa18e7 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -0,0 +1,12 @@ +getDatacenters = [ + { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + { + "id": 449494, + "longName": "Dallas 9", + "name": "dal09" + } +] \ No newline at end of file diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 11b998935..4ff3faa86 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -7,32 +7,27 @@ """ from SoftLayer import exceptions from SoftLayer.managers import storage_utils +from SoftLayer.managers.storage import StorageManager + from SoftLayer import utils # pylint: disable=too-many-public-methods -class BlockStorageManager(utils.IdentifierMixin, object): +class BlockStorageManager(StorageManager): """Manages SoftLayer Block Storage volumes. See product information here: http://www.softlayer.com/block-storage - - :param SoftLayer.API.BaseClient client: the client instance """ - def __init__(self, client): - self.configuration = {} - self.client = client - def list_block_volume_limit(self): """Returns a list of block volume count limit. :return: Returns a list of block volume count limit. """ - return self.client.call('Network_Storage', 'getVolumeCountLimits') + return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, - storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of block volumes. :param datacenter: Datacenter short name (e.g.: dal09) @@ -84,35 +79,8 @@ def get_block_volume_details(self, volume_id, **kwargs): :param kwargs: :return: Returns details about the specified volume. """ + return self.get_volume_details(volume_id, **kwargs) - if 'mask' not in kwargs: - items = [ - 'id', - 'username', - 'password', - 'capacityGb', - 'snapshotCapacityGb', - 'parentVolume.snapshotSizeBytes', - 'storageType.keyName', - 'serviceResource.datacenter[name]', - 'serviceResourceBackendIpAddress', - 'storageTierLevel', - 'provisionedIops', - 'lunId', - 'originalVolumeName', - 'originalSnapshotName', - 'originalVolumeSize', - 'activeTransactionCount', - 'activeTransactions.transactionStatus[friendlyName]', - 'replicationPartnerCount', - 'replicationStatus', - 'replicationPartners[id,username,' - 'serviceResourceBackendIpAddress,' - 'serviceResource[datacenter[name]],' - 'replicationSchedule[type[keyname]]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) def get_block_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -121,17 +89,7 @@ def get_block_volume_access_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of authorized hosts for a specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', - 'allowedHardware[allowedHost[credential]]', - 'allowedSubnets[allowedHost[credential]]', - 'allowedIpAddresses[allowedHost[credential]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_access_list(volume_id, **kwargs) def get_block_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -140,73 +98,8 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of snapshots for the specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'notes', - 'snapshotSizeBytes', - 'storageType[keyName]', - 'snapshotCreationTimestamp', - 'intervalSchedule', - 'hourlySchedule', - 'dailySchedule', - 'weeklySchedule' - ] + return self.get_volume_snapshot_list(volume_id, **kwargs) - kwargs['mask'] = ','.join(items) - - return self.client.call('Network_Storage', 'getSnapshots', - id=volume_id, **kwargs) - - def authorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - **kwargs): - """Authorizes hosts to Block Storage Volumes - - :param volume_id: The Block volume to authorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which now have access to the given Block volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - None) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def deauthorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - **kwargs): - """Revokes authorization of hosts to Block Storage Volumes - - :param volume_id: The Block volume to deauthorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which have access to the given Block volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - None) - - return self.client.call('Network_Storage', 'removeAccessFromHostList', - host_templates, id=volume_id, **kwargs) def assign_subnets_to_acl(self, access_id, subnet_ids): """Assigns subnet records to ACL for the access host. @@ -217,10 +110,7 @@ def assign_subnets_to_acl(self, access_id, subnet_ids): :param list subnet_ids: The ids of the subnets to be assigned :return: Returns int array of assigned subnet ids """ - return self.client.call('Network_Storage_Allowed_Host', - 'assignSubnetsToAcl', - subnet_ids, - id=access_id) + return self.client.call('Network_Storage_Allowed_Host', 'assignSubnetsToAcl', subnet_ids, id=access_id) def remove_subnets_from_acl(self, access_id, subnet_ids): """Removes subnet records from ACL for the access host. @@ -231,10 +121,7 @@ def remove_subnets_from_acl(self, access_id, subnet_ids): :param list subnet_ids: The ids of the subnets to be removed :return: Returns int array of removed subnet ids """ - return self.client.call('Network_Storage_Allowed_Host', - 'removeSubnetsFromAcl', - subnet_ids, - id=access_id) + return self.client.call('Network_Storage_Allowed_Host', 'removeSubnetsFromAcl', subnet_ids, id=access_id) def get_subnets_in_acl(self, access_id): """Returns a list of subnet records for the access host. @@ -244,148 +131,7 @@ def get_subnets_in_acl(self, access_id): :param integer access_id: id of the access host :return: Returns an array of SoftLayer_Network_Subnet objects """ - return self.client.call('Network_Storage_Allowed_Host', - 'getSubnetsInAcl', - id=access_id) - - def get_replication_partners(self, volume_id): - """Acquires list of replicant volumes pertaining to the given volume. - - :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) - - def get_replication_locations(self, volume_id): - """Acquires list of the datacenters to which a volume can be replicated. - - :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): - """Places an order for a replicant block volume. - - :param volume_id: The ID of the primary volume to be replicated - :param snapshot_schedule: The primary volume's snapshot - schedule to use for replication - :param location: The location for the ordered replicant volume - :param tier: The tier (IOPS per GB) of the primary volume - :param os_type: The OS type of the primary volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - block_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ - 'weeklySchedule,storageType[keyName],provisionedIops' - block_volume = self.get_block_volume_details(volume_id, - mask=block_mask) - - if os_type is None: - if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), - str): - os_type = block_volume['osType']['keyName'] - else: - raise exceptions.SoftLayerError( - "Cannot find primary volume's os-type " - "automatically; must specify manually") - - order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, block_volume, 'block' - ) - - order['osFormatType'] = {'keyName': os_type} - - 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): - """Places an order for a duplicate block volume. - - :param origin_volume_id: The ID of the origin volume to be duplicated - :param origin_snapshot_id: Origin snapshot ID to use for duplication - :param duplicate_size: Size/capacity for the duplicate volume - :param duplicate_iops: The IOPS per GB for the duplicate volume - :param duplicate_tier_level: Tier level for the duplicate volume - :param duplicate_snapshot_size: Snapshot space size for the duplicate - :param hourly_billing_flag: Billing type, monthly (False) - or hourly (True), default to monthly. - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,osType[keyName],'\ - 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_block_volume_details(origin_volume_id, - mask=block_mask) - - 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") - - order = storage_utils.prepare_duplicate_order_object( - self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'block', - hourly_billing_flag - ) - - order['osFormatType'] = {'keyName': os_type} - - if origin_snapshot_id is not None: - order['duplicateOriginSnapshotId'] = origin_snapshot_id - - 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): - """Places an order for modifying an existing block volume. - - :param volume_id: The ID of the volume to be modified - :param new_size: The new size/capacity for the volume - :param new_iops: The new IOPS for the volume - :param new_tier_level: The new tier level for the volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - mask_items = [ - 'id', - 'billingItem', - 'storageType[keyName]', - 'capacityGb', - 'provisionedIops', - 'storageTierLevel', - 'staasVersion', - 'hasEncryptionAtRest', - ] - block_mask = ','.join(mask_items) - volume = self.get_block_volume_details(volume_id, mask=block_mask) - - order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) - - return self.client.call('Product_Order', 'placeOrder', order) - - 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_Allowed_Host', 'getSubnetsInAcl', id=access_id) def order_block_volume(self, storage_type, location, size, os_type, iops=None, tier_level=None, snapshot_size=None, @@ -415,182 +161,14 @@ def order_block_volume(self, storage_type, location, size, os_type, return self.client.call('Product_Order', 'placeOrder', order) - def create_snapshot(self, volume_id, notes='', **kwargs): - """Creates a snapshot on the given block volume. - - :param integer volume_id: The id of the volume - :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): - """Orders snapshot space for the given block volume. - - :param integer volume_id: The id of the volume - :param integer capacity: The capacity to order, in GB - :param float tier: The tier level of the block volume, in IOPS per GB - :param boolean upgrade: Flag to indicate if this order is an upgrade - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - block_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' - block_volume = self.get_block_volume_details(volume_id, - mask=block_mask, - **kwargs) - - order = storage_utils.prepare_snapshot_order_object( - self, block_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): - """Cancels snapshot space for a given volume. - - :param integer volume_id: The volume ID - :param string reason: The reason for cancellation - :param boolean immediate_flag: Cancel immediately or on anniversary date - """ - - block_volume = self.get_block_volume_details( - volume_id, - mask='mask[id,billingItem[activeChildren,hourlyFlag]]') - - if 'activeChildren' not in block_volume['billingItem']: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - children_array = block_volume['billingItem']['activeChildren'] - billing_item_id = None - - for child in children_array: - if child['categoryCode'] == 'storage_snapshot_space': - billing_item_id = child['id'] - break - - if not billing_item_id: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['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): - """Enables snapshots for a specific block volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :param integer retention_count: Number of snapshots to be kept - :param integer minute: Minute when to take snapshot - :param integer hour: Hour when to take snapshot - :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) - - def disable_snapshots(self, volume_id, schedule_type): - """Disables snapshots for a specific block volume at a given schedule - - :param integer volume_id: The id of the volume - :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) - - def list_volume_schedules(self, volume_id): - """Lists schedules for a given volume - - :param integer volume_id: The id of the volume - :return: Returns list of schedules assigned to a given volume - """ - volume_detail = self.client.call( - 'Network_Storage', - 'getObject', - id=volume_id, - mask='schedules[type,properties[type]]') - - return utils.lookup(volume_detail, 'schedules') - - def restore_from_snapshot(self, volume_id, snapshot_id): - """Restores a specific volume from a snapshot - - :param integer volume_id: The id of the volume - :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) - - def cancel_block_volume(self, volume_id, - reason='No longer needed', - immediate=False): + def cancel_block_volume(self, volume_id, reason='No longer needed', immediate=False): """Cancels the given block storage volume. :param integer volume_id: The volume ID :param string reason: The reason for cancellation :param boolean immediate_flag: Cancel immediately or on anniversary date """ - block_volume = self.get_block_volume_details( - volume_id, - mask='mask[id,billingItem[id,hourlyFlag]]') - - if 'billingItem' not in block_volume: - raise exceptions.SoftLayerError("Block Storage was already cancelled") - - billing_item_id = block_volume['billingItem']['id'] - - if utils.lookup(block_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def failover_to_replicant(self, volume_id, replicant_id): - """Failover to a volume replicant. - - :param integer volume_id: The id of the volume - :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) - - def failback_from_replicant(self, volume_id): - """Failback from a volume replicant. - - :param integer volume_id: The id of the volume - :return: Returns whether failback was successful or not - """ - - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + return self.cancel_volume(volume_id, reason, immediate) def set_credential_password(self, access_id, password): """Sets the password for an access host @@ -609,5 +187,4 @@ def create_or_update_lun_id(self, volume_id, lun_id): :param integer lun_id: LUN ID to set on the volume :return: a SoftLayer_Network_Storage_Property object """ - return self.client.call('Network_Storage', 'createOrUpdateLunId', - lun_id, id=volume_id) + return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 95cf85c88..367ce559a 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -7,27 +7,23 @@ """ from SoftLayer import exceptions from SoftLayer.managers import storage_utils +from SoftLayer.managers.storage import StorageManager from SoftLayer import utils # pylint: disable=too-many-public-methods -class FileStorageManager(utils.IdentifierMixin, object): +class FileStorageManager(StorageManager): """Manages file Storage volumes.""" - def __init__(self, client): - self.configuration = {} - self.client = client - def list_file_volume_limit(self): - """Returns a list of file volume count limit. + """Returns a list of block volume count limit. - :return: Returns a list of file volume count limit. + :return: Returns a list of block volume count limit. """ - return self.client.call('Network_Storage', 'getVolumeCountLimits') + return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, - storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): """Returns a list of file volumes. :param datacenter: Datacenter short name (e.g.: dal09) @@ -109,8 +105,7 @@ def get_file_volume_details(self, volume_id, **kwargs): 'replicationSchedule[type[keyname]]]', ] kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_details(volume_id, **kwargs) def get_file_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -119,17 +114,7 @@ def get_file_volume_access_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of authorized hosts for a specified volume. """ - if 'mask' not in kwargs: - items = [ - 'id', - 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', - 'allowedHardware[allowedHost[credential]]', - 'allowedSubnets[allowedHost[credential]]', - 'allowedIpAddresses[allowedHost[credential]]', - ] - kwargs['mask'] = ','.join(items) - return self.client.call('Network_Storage', 'getObject', - id=volume_id, **kwargs) + return self.get_volume_access_list(volume_id, **kwargs) def get_file_volume_snapshot_list(self, volume_id, **kwargs): """Returns a list of snapshots for the specified volume. @@ -138,195 +123,8 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): :param kwargs: :return: Returns a list of snapshots for the specified volume. """ - if 'mask' not in kwargs: - items = [ - '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) - - def authorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - subnet_ids=None, - **kwargs): - """Authorizes hosts to File Storage Volumes - - :param volume_id: The File volume to authorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :param subnet_ids: A List of SoftLayer_Network_Subnet ids - :return: Returns an array of - SoftLayer_Network_Storage_Allowed_Host objects - which now have access to the given File volume - """ - host_templates = [] - storage_utils.populate_host_templates(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - subnet_ids) - - return self.client.call('Network_Storage', 'allowAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def deauthorize_host_to_volume(self, volume_id, - hardware_ids=None, - virtual_guest_ids=None, - ip_address_ids=None, - subnet_ids=None, - **kwargs): - """Revokes authorization of hosts to File Storage Volumes - - :param volume_id: The File volume to deauthorize hosts to - :param hardware_ids: A List of SoftLayer_Hardware ids - :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids - :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids - :param subnet_ids: A List of SoftLayer_Network_Subnet ids - :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(host_templates, - hardware_ids, - virtual_guest_ids, - ip_address_ids, - subnet_ids) - - return self.client.call('Network_Storage', 'removeAccessFromHostList', - host_templates, id=volume_id, **kwargs) - - def order_replicant_volume(self, volume_id, snapshot_schedule, - location, tier=None): - """Places an order for a replicant file volume. - - :param volume_id: The ID of the primary volume to be replicated - :param snapshot_schedule: The primary volume's snapshot - schedule to use for replication - :param location: The location for the ordered replicant volume - :param tier: The tier (IOPS per GB) of the primary volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - file_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ - 'weeklySchedule,storageType[keyName],provisionedIops' - file_volume = self.get_file_volume_details(volume_id, - mask=file_mask) - - order = storage_utils.prepare_replicant_order_object( - self, snapshot_schedule, location, tier, file_volume, 'file' - ) - - return self.client.call('Product_Order', 'placeOrder', order) - - def get_replication_partners(self, volume_id): - """Acquires list of replicant volumes pertaining to the given volume. - - :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) - - def get_replication_locations(self, volume_id): - """Acquires list of the datacenters to which a volume can be replicated. - - :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_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): - """Places an order for a duplicate file volume. - - :param origin_volume_id: The ID of the origin volume to be duplicated - :param origin_snapshot_id: Origin snapshot ID to use for duplication - :param duplicate_size: Size/capacity for the duplicate volume - :param duplicate_iops: The IOPS per GB for the duplicate volume - :param duplicate_tier_level: Tier level for the duplicate volume - :param duplicate_snapshot_size: Snapshot space size for the duplicate - :param hourly_billing_flag: Billing type, monthly (False) - or hourly (True), default to monthly. - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ + return self.get_volume_snapshot_list(volume_id, **kwargs) - file_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,'\ - 'staasVersion,hasEncryptionAtRest' - origin_volume = self.get_file_volume_details(origin_volume_id, - mask=file_mask) - - order = storage_utils.prepare_duplicate_order_object( - self, origin_volume, duplicate_iops, duplicate_tier_level, - duplicate_size, duplicate_snapshot_size, 'file', - hourly_billing_flag - ) - - if origin_snapshot_id is not None: - order['duplicateOriginSnapshotId'] = origin_snapshot_id - - 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): - """Places an order for modifying an existing file volume. - - :param volume_id: The ID of the volume to be modified - :param new_size: The new size/capacity for the volume - :param new_iops: The new IOPS for the volume - :param new_tier_level: The new tier level for the volume - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - - mask_items = [ - 'id', - 'billingItem', - 'storageType[keyName]', - 'capacityGb', - 'provisionedIops', - 'storageTierLevel', - 'staasVersion', - 'hasEncryptionAtRest', - ] - file_mask = ','.join(mask_items) - volume = self.get_file_volume_details(volume_id, mask=file_mask) - - order = storage_utils.prepare_modify_order_object( - self, volume, new_iops, new_tier_level, new_size - ) - - return self.client.call('Product_Order', 'placeOrder', order) - - 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) def order_file_volume(self, storage_type, location, size, iops=None, tier_level=None, snapshot_size=None, @@ -353,132 +151,6 @@ def order_file_volume(self, storage_type, location, size, return self.client.call('Product_Order', 'placeOrder', order) - def create_snapshot(self, volume_id, notes='', **kwargs): - """Creates a snapshot on the given file volume. - - :param integer volume_id: The id of the volume - :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 enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs): - """Enables snapshots for a specific file volume at a given schedule - - :param integer volume_id: The id of the volume - :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' - :param integer retention_count: The number of snapshots to attempt to retain in this schedule - :param integer minute: The minute of the hour at which HOURLY, DAILY, and WEEKLY snapshots should be taken - :param integer hour: The hour of the day at which DAILY and WEEKLY snapshots should be taken - :param string|integer day_of_week: The day of the week on which WEEKLY snapshots should be taken, - either as a string ('SUNDAY') or integer ('0' is Sunday) - :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) - - def disable_snapshots(self, volume_id, schedule_type): - """Disables snapshots for a specific file volume at a given schedule - - :param integer volume_id: The id of the volume - :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) - - def list_volume_schedules(self, volume_id): - """Lists schedules for a given volume - - :param integer volume_id: The id of the volume - :return: Returns list of schedules assigned to a given volume - """ - volume_detail = self.client.call( - 'Network_Storage', - 'getObject', - id=volume_id, - mask='schedules[type,properties[type]]') - - return utils.lookup(volume_detail, 'schedules') - - def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): - """Orders snapshot space for the given file volume. - - :param integer volume_id: The ID of the volume - :param integer capacity: The capacity to order, in GB - :param float tier: The tier level of the file volume, in IOPS per GB - :param boolean upgrade: Flag to indicate if this order is an upgrade - :return: Returns a SoftLayer_Container_Product_Order_Receipt - """ - file_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' - file_volume = self.get_file_volume_details(volume_id, - mask=file_mask, - **kwargs) - - order = storage_utils.prepare_snapshot_order_object( - self, file_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): - """Cancels snapshot space for a given volume. - - :param integer volume_id: The volume ID - :param string reason: The reason for cancellation - :param boolean immediate: Cancel immediately or on anniversary date - """ - - file_volume = self.get_file_volume_details( - volume_id, - mask='mask[id,billingItem[activeChildren,hourlyFlag]]') - - if 'activeChildren' not in file_volume['billingItem']: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - children_array = file_volume['billingItem']['activeChildren'] - billing_item_id = None - - for child in children_array: - if child['categoryCode'] == 'storage_snapshot_space': - billing_item_id = child['id'] - break - - if not billing_item_id: - raise exceptions.SoftLayerError( - 'No snapshot space found to cancel') - - if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def restore_from_snapshot(self, volume_id, snapshot_id): - """Restores a specific volume from a snapshot - - :param integer volume_id: The ID of the volume - :param integer snapshot_id: The id of the restore point - :return: Returns whether successfully restored or not - """ - - return self.client.call('Network_Storage', 'restoreFromSnapshot', - snapshot_id, id=volume_id) - def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=False): """Cancels the given file storage volume. @@ -486,39 +158,6 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param string reason: The reason for cancellation :param boolean immediate: Cancel immediately or on anniversary date """ - file_volume = self.get_file_volume_details( - volume_id, - mask='mask[id,billingItem[id,hourlyFlag]]') - - if 'billingItem' not in file_volume: - raise exceptions.SoftLayerError('The volume has already been canceled') - billing_item_id = file_volume['billingItem']['id'] - - if utils.lookup(file_volume, 'billingItem', 'hourlyFlag'): - immediate = True - - return self.client['Billing_Item'].cancelItem( - immediate, - True, - reason, - id=billing_item_id) - - def failover_to_replicant(self, volume_id, replicant_id): - """Failover to a volume replicant. - - :param integer volume_id: The ID of the volume - :param integer replicant_id: ID of replicant to failover to - :return: Returns whether failover was successful or not - """ + return self.cancel_volume(volume_id, reason, immediate) - return self.client.call('Network_Storage', 'failoverToReplicant', - replicant_id, id=volume_id) - - def failback_from_replicant(self, volume_id): - """Failback from a volume replicant. - - :param integer volume_id: The ID of the volume - :return: Returns whether failback was successful or not - """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py new file mode 100644 index 000000000..dca50a531 --- /dev/null +++ b/SoftLayer/managers/storage.py @@ -0,0 +1,415 @@ +""" + SoftLayer.storage + ~~~~~~~~~~~~~~~ + Network Storage Manager + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import exceptions +from SoftLayer.managers import storage_utils +from SoftLayer import utils + +# pylint: disable=too-many-public-methods + + +class StorageManager(utils.IdentifierMixin, object): + """"Base class for File and Block storage managers + + Any shared code between File and Block should ideally go here. + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.configuration = {} + self.client = client + + def get_volume_count_limits(self): + """Returns a list of block volume count limit. + + :return: Returns a list of block volume count limit. + """ + return self.client.call('Network_Storage', 'getVolumeCountLimits') + + def get_volume_details(self, volume_id, **kwargs): + """Returns details about the specified volume. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns details about the specified volume. + """ + + if 'mask' not in kwargs: + items = [ + 'id', + 'username', + 'password', + 'capacityGb', + 'snapshotCapacityGb', + 'parentVolume.snapshotSizeBytes', + 'storageType.keyName', + 'serviceResource.datacenter[name]', + 'serviceResourceBackendIpAddress', + 'storageTierLevel', + 'provisionedIops', + 'lunId', + 'originalVolumeName', + 'originalSnapshotName', + 'originalVolumeSize', + 'activeTransactionCount', + 'activeTransactions.transactionStatus[friendlyName]', + 'replicationPartnerCount', + 'replicationStatus', + 'replicationPartners[id,username,' + 'serviceResourceBackendIpAddress,' + 'serviceResource[datacenter[name]],' + 'replicationSchedule[type[keyname]]]', + ] + kwargs['mask'] = ','.join(items) + 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. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of authorized hosts for a specified volume. + """ + if 'mask' not in kwargs: + items = [ + 'id', + 'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]', + 'allowedHardware[allowedHost[credential]]', + 'allowedSubnets[allowedHost[credential]]', + 'allowedIpAddresses[allowedHost[credential]]', + ] + kwargs['mask'] = ','.join(items) + 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. + + :param volume_id: ID of volume. + :param kwargs: + :return: Returns a list of snapshots for the specified volume. + """ + if 'mask' not in kwargs: + items = [ + '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) + + 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 + :param hardware_ids: A List of SoftLayer_Hardware ids + :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids + :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids + :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes. + :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): + """Revokes authorization of hosts to File Storage Volumes + + :param volume_id: The File volume to deauthorize hosts to + :param hardware_ids: A List of SoftLayer_Hardware ids + :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids + :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids + :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes + :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) + + 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. + + :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) + + def get_replication_locations(self, volume_id): + """Acquires list of the datacenters to which a volume can be replicated. + + :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): + """Places an order for a replicant volume. + + :param volume_id: The ID of the primary volume to be replicated + :param snapshot_schedule: The primary volume's snapshot + schedule to use for replication + :param location: The location for the ordered replicant volume + :param tier: The tier (IOPS per GB) of the primary volume + :param os_type: The OS type of the primary volume + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + block_mask = 'billingItem[activeChildren,hourlyFlag],'\ + 'storageTierLevel,osType,staasVersion,'\ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ + 'intervalSchedule,hourlySchedule,dailySchedule,'\ + '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']) + + order = storage_utils.prepare_replicant_order_object( + 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): + os_type = block_volume['osType']['keyName'] + else: + raise exceptions.SoftLayerError( + "Cannot find primary volume's os-type " + "automatically; must specify manually") + order['osFormatType'] = {'keyName': os_type} + + 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): + """Places an order for a duplicate volume. + + :param origin_volume_id: The ID of the origin volume to be duplicated + :param origin_snapshot_id: Origin snapshot ID to use for duplication + :param duplicate_size: Size/capacity for the duplicate volume + :param duplicate_iops: The IOPS per GB for the duplicate volume + :param duplicate_tier_level: Tier level for the duplicate volume + :param duplicate_snapshot_size: Snapshot space size for the duplicate + :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly. + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ + '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']) + + + 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 + ) + + if storage_class == 'block': + 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") + + order['osFormatType'] = {'keyName': os_type} + + if origin_snapshot_id is not None: + order['duplicateOriginSnapshotId'] = origin_snapshot_id + + 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): + """Places an order for modifying an existing block volume. + + :param volume_id: The ID of the volume to be modified + :param new_size: The new size/capacity for the volume + :param new_iops: The new IOPS for the volume + :param new_tier_level: The new tier level for the volume + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + + mask_items = [ + 'id', + 'billingItem', + 'storageType[keyName]', + 'capacityGb', + 'provisionedIops', + 'storageTierLevel', + 'staasVersion', + 'hasEncryptionAtRest', + ] + block_mask = ','.join(mask_items) + 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 + ) + + return self.client.call('Product_Order', 'placeOrder', order) + + + 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) + + def create_snapshot(self, volume_id, notes='', **kwargs): + """Creates a snapshot on the given block volume. + + :param integer volume_id: The id of the volume + :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): + """Orders snapshot space for the given block volume. + + :param integer volume_id: The id of the volume + :param integer capacity: The capacity to order, in GB + :param float tier: The tier level of the block volume, in IOPS per GB + :param boolean upgrade: Flag to indicate if this order is an upgrade + :return: Returns a SoftLayer_Container_Product_Order_Receipt + """ + object_mask = 'id,billingItem[location,hourlyFlag],'\ + 'storageType[keyName],storageTierLevel,provisionedIops,'\ + '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) + + return self.client.call('Product_Order', 'placeOrder', order) + + 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 + :param string reason: The reason for cancellation + :param boolean immediate_flag: Cancel immediately or on anniversary date + """ + + object_mask = 'mask[id,billingItem[activeChildren,hourlyFlag]]' + 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') + + children_array = volume['billingItem']['activeChildren'] + billing_item_id = None + + for child in children_array: + if child['categoryCode'] == 'storage_snapshot_space': + billing_item_id = child['id'] + break + + if not billing_item_id: + 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) + + 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 + :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY' + :param integer retention_count: Number of snapshots to be kept + :param integer minute: Minute when to take snapshot + :param integer hour: Hour when to take snapshot + :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) + + def disable_snapshots(self, volume_id, schedule_type): + """Disables snapshots for a specific block volume at a given schedule + + :param integer volume_id: The id of the volume + :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) + + def list_volume_schedules(self, volume_id): + """Lists schedules for a given volume + + :param integer volume_id: The id of the volume + :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) + + return utils.lookup(volume_detail, 'schedules') + + def restore_from_snapshot(self, volume_id, snapshot_id): + """Restores a specific volume from a snapshot + + :param integer volume_id: The id of the volume + :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) + + def failover_to_replicant(self, volume_id, replicant_id): + """Failover to a volume replicant. + + :param integer volume_id: The id of the volume + :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) + + def failback_from_replicant(self, volume_id): + """Failback from a volume replicant. + + :param integer volume_id: The id of the volume + :return: Returns whether failback was successful or not + """ + + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) + + def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): + """Cancels the given block storage volume. + + :param integer volume_id: The volume ID + :param string reason: The reason for cancellation + :param boolean immediate_flag: Cancel immediately or on anniversary date + """ + object_mask = 'mask[id,billingItem[id,hourlyFlag]]' + volume = self.get_volume_details(volume_id, mask=object_mask) + + if 'billingItem' not in volume: + raise exceptions.SoftLayerError("Block Storage 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) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 80ec60368..97a8c14e2 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -19,19 +19,19 @@ } -def populate_host_templates(host_templates, - hardware_ids=None, +def populate_host_templates(hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): - """Populate the given host_templates array with the IDs provided + """Returns a populated array with the IDs provided - :param host_templates: The array to which host templates will be added :param hardware_ids: A List of SoftLayer_Hardware ids :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids :param subnet_ids: A List of SoftLayer_Network_Subnet ids + :return: array of objects formatted for allowAccessFromHostList """ + host_templates = [] if hardware_ids is not None: for hardware_id in hardware_ids: host_templates.append({ @@ -59,6 +59,7 @@ def populate_host_templates(host_templates, 'objectType': 'SoftLayer_Network_Subnet', 'id': subnet_id }) + return host_templates def get_package(manager, category_code): @@ -984,6 +985,13 @@ def prepare_modify_order_object(manager, volume, new_iops, new_tier, new_size): return modify_order +def block_or_file(storage_type_keyname): + """returns either 'block' or 'file' + + :param storage_type_keyname: the Network_Storage['storageType']['keyName'] + :returns: 'block' or 'file' + """ + return 'block' if 'BLOCK_STORAGE' in storage_type_keyname else 'file' def _has_category(categories, category_code): return any( @@ -1014,3 +1022,4 @@ def _find_price_id(prices, category, restriction_type=None, restriction_value=No continue return {'id': price['id']} + diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index acb0dc6f6..400ba0e19 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -8,7 +8,11 @@ import copy import SoftLayer from SoftLayer import exceptions -from SoftLayer import fixtures +from SoftLayer.fixtures import SoftLayer_Account +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Network_Storage +from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host from SoftLayer import testing @@ -83,7 +87,7 @@ def test_cancel_block_volume_billing_item_found(self): def test_get_block_volume_details(self): result = self.block.get_block_volume_details(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) expected_mask = 'id,' \ 'username,' \ @@ -119,7 +123,7 @@ def test_get_block_volume_details(self): def test_list_block_volumes(self): result = self.block.list_block_volumes() - self.assertEqual(fixtures.SoftLayer_Account.getIscsiNetworkStorage, + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, result) expected_filter = { @@ -157,7 +161,7 @@ def test_list_block_volumes_with_additional_filters(self): storage_type="Endurance", username="username") - self.assertEqual(fixtures.SoftLayer_Account.getIscsiNetworkStorage, + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, result) expected_filter = { @@ -197,7 +201,7 @@ def test_list_block_volumes_with_additional_filters(self): def test_get_block_volume_access_list(self): result = self.block.get_block_volume_access_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -207,7 +211,7 @@ def test_get_block_volume_access_list(self): def test_get_block_volume_snapshot_list(self): result = self.block.get_block_volume_snapshot_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots, + self.assertEqual(SoftLayer_Network_Storage.getSnapshots, result) self.assert_called_with( @@ -218,7 +222,7 @@ def test_get_block_volume_snapshot_list(self): def test_delete_snapshot(self): result = self.block.delete_snapshot(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject, + self.assertEqual(SoftLayer_Network_Storage.deleteObject, result) self.assert_called_with( @@ -331,7 +335,7 @@ def test_replicant_failover(self): result = self.block.failover_to_replicant(1234, 5678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) + SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', @@ -343,7 +347,7 @@ def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) + SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', @@ -373,9 +377,9 @@ def test_order_block_volume_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -389,7 +393,7 @@ def test_order_block_volume_performance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -418,9 +422,9 @@ def test_order_block_volume_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -433,7 +437,7 @@ def test_order_block_volume_endurance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -463,7 +467,7 @@ def test_authorize_host_to_volume(self): virtual_guest_ids=[200], ip_address_ids=[300]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. allowAccessFromHostList, result) self.assert_called_with( @@ -478,7 +482,7 @@ def test_deauthorize_host_to_volume(self): virtual_guest_ids=[200], ip_address_ids=[300]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. removeAccessFromHostList, result) self.assert_called_with( @@ -491,7 +495,7 @@ def test_assign_subnets_to_acl(self): 12345, subnet_ids=[12345678]) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. assignSubnetsToAcl, result) self.assert_called_with( @@ -504,7 +508,7 @@ def test_remove_subnets_from_acl(self): 12345, subnet_ids=[12345678]) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. removeSubnetsFromAcl, result) self.assert_called_with( @@ -515,7 +519,7 @@ def test_remove_subnets_from_acl(self): def test_get_subnets_in_acl(self): result = self.block.get_subnets_in_acl(12345) - self.assertEqual(fixtures.SoftLayer_Network_Storage_Allowed_Host. + self.assertEqual(SoftLayer_Network_Storage_Allowed_Host. getSubnetsInAcl, result) self.assert_called_with( @@ -526,7 +530,7 @@ def test_get_subnets_in_acl(self): def test_create_snapshot(self): result = self.block.create_snapshot(123, 'hello world') - self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot, + self.assertEqual(SoftLayer_Network_Storage.createSnapshot, result) self.assert_called_with( @@ -538,7 +542,7 @@ def test_snapshot_restore(self): result = self.block.restore_from_snapshot(12345678, 87654321) self.assertEqual( - fixtures.SoftLayer_Network_Storage.restoreFromSnapshot, + SoftLayer_Network_Storage.restoreFromSnapshot, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -549,7 +553,7 @@ def test_enable_snapshots(self): result = self.block.enable_snapshots(12345678, 'WEEKLY', 10, 47, 16, 'FRIDAY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.enableSnapshots, result) self.assert_called_with( @@ -560,7 +564,7 @@ def test_enable_snapshots(self): def test_disable_snapshots(self): result = self.block.disable_snapshots(12345678, 'HOURLY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.disableSnapshots, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -571,7 +575,7 @@ def test_list_volume_schedules(self): result = self.block.list_volume_schedules(12345678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.listVolumeSchedules, + SoftLayer_Network_Storage.listVolumeSchedules, result) expected_mask = 'schedules[type,properties[type]]' @@ -585,16 +589,16 @@ def test_list_volume_schedules(self): def test_order_block_snapshot_space_upgrade(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_snapshot_space(102, 20, None, True) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -615,15 +619,15 @@ def test_order_block_snapshot_space_upgrade(self): def test_order_block_snapshot_space(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_snapshot_space(102, 10, None, False) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -643,7 +647,10 @@ def test_order_block_snapshot_space(self): ) def test_order_block_replicant_os_type_not_found(self): - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_package = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') + mock_package.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] + + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) del mock_volume['osType'] mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -665,9 +672,9 @@ def test_order_block_replicant_performance_os_type_given(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -679,7 +686,7 @@ def test_order_block_replicant_performance_os_type_given(self): os_type='XEN' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -712,15 +719,15 @@ def test_order_block_replicant_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -749,9 +756,9 @@ def test_order_block_replicant_endurance(self): def test_order_block_duplicate_origin_os_type_not_found(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) del mock_volume['osType'] mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -767,9 +774,9 @@ def test_order_block_duplicate_origin_os_type_not_found(self): def test_order_block_duplicate_performance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -778,7 +785,7 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -804,9 +811,9 @@ def test_order_block_duplicate_performance_no_duplicate_snapshot(self): def test_order_block_duplicate_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -820,7 +827,7 @@ def test_order_block_duplicate_performance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -848,9 +855,9 @@ def test_order_block_duplicate_performance(self): def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -858,7 +865,7 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -883,9 +890,9 @@ def test_order_block_duplicate_endurance_no_duplicate_snapshot(self): def test_order_block_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -898,7 +905,7 @@ def test_order_block_duplicate_endurance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -925,16 +932,16 @@ def test_order_block_duplicate_endurance(self): def test_order_block_modified_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_modified_volume(102, new_size=1000, new_iops=2000, new_tier_level=None) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -948,15 +955,15 @@ def test_order_block_modified_performance(self): def test_order_block_modified_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME + mock_volume = SoftLayer_Network_Storage.STAAS_TEST_VOLUME mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.block.order_modified_volume(102, new_size=1000, new_iops=None, new_tier_level=4) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -976,4 +983,4 @@ def test_setCredentialPassword(self): def test_list_block_volume_limit(self): result = self.block.list_block_volume_limit() - self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) + self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index d6ca66c68..640c2d9f8 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -8,7 +8,11 @@ import copy import SoftLayer from SoftLayer import exceptions -from SoftLayer import fixtures +from SoftLayer.fixtures import SoftLayer_Account +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Network_Storage +from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host from SoftLayer import testing @@ -49,7 +53,7 @@ def test_authorize_host_to_volume(self): ip_address_ids=[300], subnet_ids=[400]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. allowAccessFromHostList, result) self.assert_called_with( @@ -65,7 +69,7 @@ def test_deauthorize_host_to_volume(self): ip_address_ids=[300], subnet_ids=[400]) - self.assertEqual(fixtures.SoftLayer_Network_Storage. + self.assertEqual(SoftLayer_Network_Storage. removeAccessFromHostList, result) self.assert_called_with( @@ -84,7 +88,7 @@ def test_enable_snapshots(self): result = self.file.enable_snapshots(12345678, 'WEEKLY', 10, 47, 16, 'FRIDAY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.enableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.enableSnapshots, result) self.assert_called_with( @@ -95,7 +99,7 @@ def test_enable_snapshots(self): def test_disable_snapshots(self): result = self.file.disable_snapshots(12345678, 'HOURLY') - self.assertEqual(fixtures.SoftLayer_Network_Storage.disableSnapshots, + self.assertEqual(SoftLayer_Network_Storage.disableSnapshots, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -106,7 +110,7 @@ def test_snapshot_restore(self): result = self.file.restore_from_snapshot(12345678, 87654321) self.assertEqual( - fixtures.SoftLayer_Network_Storage.restoreFromSnapshot, + SoftLayer_Network_Storage.restoreFromSnapshot, result) self.assert_called_with( 'SoftLayer_Network_Storage', @@ -116,7 +120,7 @@ def test_snapshot_restore(self): def test_get_file_volume_details(self): result = self.file.get_file_volume_details(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getObject, result) + self.assertEqual(SoftLayer_Network_Storage.getObject, result) expected_mask = 'id,'\ 'username,'\ @@ -153,7 +157,7 @@ def test_get_file_volume_details(self): def test_get_file_volume_snapshot_list(self): result = self.file.get_file_volume_snapshot_list(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.getSnapshots, + self.assertEqual(SoftLayer_Network_Storage.getSnapshots, result) self.assert_called_with( @@ -164,7 +168,7 @@ def test_get_file_volume_snapshot_list(self): def test_create_snapshot(self): result = self.file.create_snapshot(123, 'hello world') - self.assertEqual(fixtures.SoftLayer_Network_Storage.createSnapshot, + self.assertEqual(SoftLayer_Network_Storage.createSnapshot, result) self.assert_called_with( @@ -277,7 +281,7 @@ def test_replicant_failover(self): result = self.file.failover_to_replicant(1234, 5678) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failoverToReplicant, result) + SoftLayer_Network_Storage.failoverToReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failoverToReplicant', @@ -289,7 +293,7 @@ def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) self.assertEqual( - fixtures.SoftLayer_Network_Storage.failbackFromReplicant, result) + SoftLayer_Network_Storage.failbackFromReplicant, result) self.assert_called_with( 'SoftLayer_Network_Storage', 'failbackFromReplicant', @@ -317,7 +321,7 @@ def test_get_replication_locations(self): def test_delete_snapshot(self): result = self.file.delete_snapshot(100) - self.assertEqual(fixtures.SoftLayer_Network_Storage.deleteObject, + self.assertEqual(SoftLayer_Network_Storage.deleteObject, result) self.assert_called_with( @@ -328,7 +332,7 @@ def test_delete_snapshot(self): def test_list_file_volumes(self): result = self.file.list_file_volumes() - self.assertEqual(fixtures.SoftLayer_Account.getNasNetworkStorage, + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, result) expected_filter = { @@ -366,7 +370,7 @@ def test_list_file_volumes_with_additional_filters(self): storage_type="Endurance", username="username") - self.assertEqual(fixtures.SoftLayer_Account.getNasNetworkStorage, + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, result) expected_filter = { @@ -408,9 +412,9 @@ def test_order_file_volume_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -423,7 +427,7 @@ def test_order_file_volume_performance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -451,9 +455,9 @@ def test_order_file_volume_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -466,7 +470,7 @@ def test_order_file_volume_endurance(self): service_offering='storage_as_a_service' ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -490,16 +494,16 @@ def test_order_file_volume_endurance(self): def test_order_file_snapshot_space_upgrade(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_snapshot_space(102, 20, None, True) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -520,16 +524,16 @@ def test_order_file_snapshot_space_upgrade(self): def test_order_file_snapshot_space(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_snapshot_space(102, 10, None, False) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -553,16 +557,16 @@ def test_order_file_replicant_performance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -594,16 +598,16 @@ def test_order_file_replicant_endurance(self): mock.return_value = [{'id': 449494, 'name': 'dal09'}] mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_replicant_volume(102, 'WEEKLY', 'dal09') - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -631,9 +635,9 @@ def test_order_file_replicant_endurance(self): def test_order_file_duplicate_performance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -642,7 +646,7 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -667,8 +671,8 @@ def test_order_file_duplicate_performance_no_duplicate_snapshot(self): def test_order_file_duplicate_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -682,7 +686,7 @@ def test_order_file_duplicate_performance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -709,9 +713,9 @@ def test_order_file_duplicate_performance(self): def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -720,7 +724,7 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): 102, duplicate_snapshot_size=0) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -744,9 +748,9 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): def test_order_file_duplicate_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -760,7 +764,7 @@ def test_order_file_duplicate_endurance(self): duplicate_snapshot_size=10 ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', @@ -786,16 +790,16 @@ def test_order_file_duplicate_endurance(self): def test_order_file_modified_performance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_modified_volume(102, new_size=1000, new_iops=2000, new_tier_level=None) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -809,16 +813,16 @@ def test_order_file_modified_performance(self): def test_order_file_modified_endurance(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume result = self.file.order_modified_volume(102, new_size=1000, new_iops=None, new_tier_level=4) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', 'placeOrder', @@ -831,4 +835,4 @@ def test_order_file_modified_endurance(self): def test_list_file_volume_limit(self): result = self.file.list_file_volume_limit() - self.assertEqual(fixtures.SoftLayer_Network_Storage.getVolumeCountLimits, result) + self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) From ef96801cc6c55bc2253ce27b6968b49350f67861 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:19:06 -0600 Subject: [PATCH 0267/1582] style and tox fixes --- .../fixtures/SoftLayer_Location_Datacenter.py | 2 +- SoftLayer/managers/block.py | 6 +----- SoftLayer/managers/file.py | 6 +----- SoftLayer/managers/storage.py | 13 +++++-------- SoftLayer/managers/storage_utils.py | 3 ++- tests/managers/block_tests.py | 4 ++-- tests/managers/file_tests.py | 5 ++--- tests/managers/storage_utils_tests.py | 16 +++------------- 8 files changed, 17 insertions(+), 38 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py index 510fa18e7..e9aa9b48e 100644 --- a/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py +++ b/SoftLayer/fixtures/SoftLayer_Location_Datacenter.py @@ -9,4 +9,4 @@ "longName": "Dallas 9", "name": "dal09" } -] \ No newline at end of file +] diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 4ff3faa86..45091c002 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -5,10 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions -from SoftLayer.managers import storage_utils from SoftLayer.managers.storage import StorageManager - +from SoftLayer.managers import storage_utils from SoftLayer import utils # pylint: disable=too-many-public-methods @@ -81,7 +79,6 @@ def get_block_volume_details(self, volume_id, **kwargs): """ return self.get_volume_details(volume_id, **kwargs) - def get_block_volume_access_list(self, volume_id, **kwargs): """Returns a list of authorized hosts for a specified volume. @@ -100,7 +97,6 @@ def get_block_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(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 367ce559a..b594209d4 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -5,9 +5,8 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions -from SoftLayer.managers import storage_utils from SoftLayer.managers.storage import StorageManager +from SoftLayer.managers import storage_utils from SoftLayer import utils # pylint: disable=too-many-public-methods @@ -125,7 +124,6 @@ def get_file_volume_snapshot_list(self, volume_id, **kwargs): """ return self.get_volume_snapshot_list(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', @@ -159,5 +157,3 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param boolean immediate: Cancel immediately or on anniversary date """ return self.cancel_volume(volume_id, reason, immediate) - - diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index dca50a531..7d4f74562 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -139,8 +139,8 @@ 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) @@ -182,8 +182,8 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No 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: @@ -219,7 +219,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, 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 @@ -238,7 +237,6 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, 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): """Places an order for modifying an existing block volume. @@ -268,7 +266,6 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie return self.client.call('Product_Order', 'placeOrder', order) - def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. @@ -297,7 +294,7 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): object_mask = 'id,billingItem[location,hourlyFlag],'\ 'storageType[keyName],storageTierLevel,provisionedIops,'\ 'staasVersion,hasEncryptionAtRest' - volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) + volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs) order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade) diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 97a8c14e2..021fc713f 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -985,6 +985,7 @@ def prepare_modify_order_object(manager, volume, new_iops, new_tier, new_size): return modify_order + def block_or_file(storage_type_keyname): """returns either 'block' or 'file' @@ -993,6 +994,7 @@ def block_or_file(storage_type_keyname): """ return 'block' if 'BLOCK_STORAGE' in storage_type_keyname else 'file' + def _has_category(categories, category_code): return any( True @@ -1022,4 +1024,3 @@ def _find_price_id(prices, category, restriction_type=None, restriction_value=No continue return {'id': price['id']} - diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 400ba0e19..633cd21c0 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -9,10 +9,10 @@ import SoftLayer from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Account -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer.fixtures import SoftLayer_Network_Storage from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 640c2d9f8..8a2db96df 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -9,10 +9,9 @@ import SoftLayer from SoftLayer import exceptions from SoftLayer.fixtures import SoftLayer_Account -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer.fixtures import SoftLayer_Network_Storage -from SoftLayer.fixtures import SoftLayer_Network_Storage_Allowed_Host +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/storage_utils_tests.py b/tests/managers/storage_utils_tests.py index f6934edaf..976f3749a 100644 --- a/tests/managers/storage_utils_tests.py +++ b/tests/managers/storage_utils_tests.py @@ -23,30 +23,20 @@ def set_up(self): # Tests for populate_host_templates() # --------------------------------------------------------------------- def test_populate_host_templates_no_ids_given(self): - host_templates = [] - - storage_utils.populate_host_templates(host_templates) - + host_templates = storage_utils.populate_host_templates() self.assertEqual([], host_templates) def test_populate_host_templates_empty_arrays_given(self): - host_templates = [] - - storage_utils.populate_host_templates( - host_templates, + host_templates = storage_utils.populate_host_templates( hardware_ids=[], virtual_guest_ids=[], ip_address_ids=[], subnet_ids=[] ) - self.assertEqual([], host_templates) def test_populate_host_templates(self): - host_templates = [] - - storage_utils.populate_host_templates( - host_templates, + host_templates = storage_utils.populate_host_templates( hardware_ids=[1111], virtual_guest_ids=[2222], ip_address_ids=[3333], From 1c812ded1233e9fa8230da510ad913442d4a2606 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:29:28 -0600 Subject: [PATCH 0268/1582] resolved merge conflicts with master --- SoftLayer/managers/storage.py | 9 ++++++--- tests/managers/block_tests.py | 6 +++--- tests/managers/file_tests.py | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 7d4f74562..c9768730a 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -197,9 +197,9 @@ 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): + 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 @@ -234,6 +234,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, if origin_snapshot_id is not None: order['duplicateOriginSnapshotId'] = origin_snapshot_id + if dependent_duplicate: + # if isDependentDuplicateFlag is set to ANYTHING, it is considered dependent. + order['isDependentDuplicateFlag'] = 1 return self.client.call('Product_Order', 'placeOrder', order) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 911a5f766..30a767aaf 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -855,9 +855,9 @@ def test_order_block_duplicate_performance(self): def test_order_block_duplicate_depdupe(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'PERFORMANCE_BLOCK_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -872,7 +872,7 @@ def test_order_block_duplicate_depdupe(self): dependent_duplicate=True ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 2011ff327..bdfe9aafd 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -747,9 +747,9 @@ def test_order_file_duplicate_endurance_no_duplicate_snapshot(self): def test_order_file_duplicate_depdupe(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = [fixtures.SoftLayer_Product_Package.SAAS_PACKAGE] + mock.return_value = [SoftLayer_Product_Package.SAAS_PACKAGE] - mock_volume = copy.deepcopy(fixtures.SoftLayer_Network_Storage.STAAS_TEST_VOLUME) + mock_volume = copy.deepcopy(SoftLayer_Network_Storage.STAAS_TEST_VOLUME) mock_volume['storageType']['keyName'] = 'ENDURANCE_FILE_STORAGE' mock = self.set_mock('SoftLayer_Network_Storage', 'getObject') mock.return_value = mock_volume @@ -764,7 +764,7 @@ def test_order_file_duplicate_depdupe(self): dependent_duplicate=True ) - self.assertEqual(fixtures.SoftLayer_Product_Order.placeOrder, result) + self.assertEqual(SoftLayer_Product_Order.placeOrder, result) self.assert_called_with( 'SoftLayer_Product_Order', From 63219ac10af22c0f6f6eded39a3683487983cf06 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:51:07 -0600 Subject: [PATCH 0269/1582] Merged in #1232 and added it to the refactor --- SoftLayer/CLI/block/detail.py | 17 ---------- SoftLayer/CLI/file/detail.py | 17 ---------- SoftLayer/managers/block.py | 7 ++++ SoftLayer/managers/file.py | 7 ++++ SoftLayer/managers/storage.py | 4 +++ tests/CLI/modules/block_tests.py | 56 +++++++++----------------------- tests/CLI/modules/file_tests.py | 56 +++++++++----------------------- 7 files changed, 50 insertions(+), 114 deletions(-) diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 02ce0c82f..2e7b115e7 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -9,29 +9,12 @@ from SoftLayer import utils -def get_block_volume_id(volume_id, block_manager): - """Returns the volume id. - - :param volume_id: ID of volume. - :param block_manager: Block Storage Manager. - :return: Returns the volume id. - """ - storage_list = block_manager.list_block_volumes() - for storage in storage_list: - if volume_id == storage['username']: - volume_id = storage['id'] - break - - return volume_id - - @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) - volume_id = get_block_volume_id(volume_id, block_manager) block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') block_volume = block_manager.get_block_volume_details(block_volume_id) block_volume = utils.NestedDict(block_volume) diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index ad0393916..cea86e351 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -9,29 +9,12 @@ from SoftLayer import utils -def get_file_volume_id(volume_id, file_manager): - """Returns the volume id. - - :param volume_id: ID of volume. - :param block_manager: Block Storage Manager. - :return: Returns the volume id. - """ - storage_list = file_manager.list_file_volumes() - for storage in storage_list: - if volume_id == storage['username']: - volume_id = storage['id'] - break - - return volume_id - - @click.command() @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): """Display details for a specified volume.""" file_manager = SoftLayer.FileStorageManager(env.client) - volume_id = get_file_volume_id(volume_id, file_manager) file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') file_volume = file_manager.get_file_volume_details(file_volume_id) file_volume = utils.NestedDict(file_volume) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 45091c002..eec22a4bb 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -184,3 +184,10 @@ def create_or_update_lun_id(self, volume_id, lun_id): :return: a SoftLayer_Network_Storage_Property object """ return self.client.call('Network_Storage', 'createOrUpdateLunId', lun_id, id=volume_id) + + def _get_ids_from_username(self, username): + object_mask = "mask[id]" + results = self.list_block_volumes(username=username, mask=object_mask) + if results: + return [result['id'] for result in results] + diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index b594209d4..458ffa9aa 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -157,3 +157,10 @@ def cancel_file_volume(self, volume_id, reason='No longer needed', immediate=Fal :param boolean immediate: Cancel immediately or on anniversary date """ return self.cancel_volume(volume_id, reason, immediate) + + def _get_ids_from_username(self, username): + object_mask = "mask[id]" + results = self.list_file_volumes(username=username, mask=object_mask) + if results: + return [result['id'] for result in results] + diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index c9768730a..7c927362c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -23,6 +23,10 @@ class StorageManager(utils.IdentifierMixin, object): def __init__(self, client): self.configuration = {} self.client = client + self.resolvers = [self._get_ids_from_username] + + def _get_ids_from_username(self, username): + raise exceptions.SoftLayerError("Not Implemented.") def get_volume_count_limits(self): """Returns a list of block volume count limit. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 42e574c48..9e5774400 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -60,6 +60,7 @@ def test_volume_detail(self): self.assert_no_fail(result) isinstance(json.loads(result.output)['IOPs'], float) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1234) self.assertEqual({ 'Username': 'username', 'LUN Id': '2', @@ -99,47 +100,22 @@ def test_volume_detail(self): }, json.loads(result.output)) def test_volume_detail_name_identifier(self): - result = self.run_command(['block', 'volume-detail', 'username']) - + result = self.run_command(['block', 'volume-detail', 'SL-12345']) + expected_filter = { + 'iscsiNetworkStorage': { + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ ISCSI'} + } + }, + 'storageType': { + 'keyName': {'operation': '*= BLOCK_STORAGE'} + }, + 'username': {'operation': '_= SL-12345'}}} + + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=100) self.assert_no_fail(result) - isinstance(json.loads(result.output)['IOPs'], float) - self.assertEqual({ - 'Username': 'username', - 'LUN Id': '2', - 'Endurance Tier': 'READHEAVY_TIER', - 'IOPs': 1000, - 'Snapshot Capacity (GB)': '10', - 'Snapshot Used (Bytes)': 1024, - 'Capacity (GB)': '20GB', - 'Target IP': '10.1.2.3', - 'Data Center': 'dal05', - 'Type': 'ENDURANCE', - 'ID': 100, - '# of Active Transactions': '1', - 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', - 'Replicant Count': '1', - 'Replication Status': 'Replicant Volume Provisioning ' - 'has completed.', - 'Replicant Volumes': [[ - {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, - {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, - {'Replicant ID': 'Data Center', '1784': 'wdc01'}, - {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, - ], [ - {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, - {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, - {'Replicant ID': 'Data Center', '1785': 'dal01'}, - {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]], - 'Original Volume Properties': [ - {'Property': 'Original Volume Size', - 'Value': '20'}, - {'Property': 'Original Volume Name', - 'Value': 'test-original-volume-name'}, - {'Property': 'Original Snapshot Name', - 'Value': 'test-original-snapshot-name'} - ] - }, json.loads(result.output)) def test_volume_list(self): result = self.run_command(['block', 'volume-list']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index e5e3ca556..b37fb0c3f 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -164,47 +164,23 @@ def test_volume_detail(self): }, json.loads(result.output)) def test_volume_detail_name_identifier(self): - result = self.run_command(['file', 'volume-detail', 'user']) - + result = self.run_command(['file', 'volume-detail', 'SL-12345']) + expected_filter = { + 'nasNetworkStorage': { + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ NAS'} + } + }, + 'storageType': { + 'keyName': {'operation': '*= FILE_STORAGE'} + }, + 'username': {'operation': '_= SL-12345'}}} + + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage', filter=expected_filter) + self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1) self.assert_no_fail(result) - self.assertEqual({ - 'Username': 'username', - 'Used Space': '0B', - 'Endurance Tier': 'READHEAVY_TIER', - 'IOPs': 1000, - 'Mount Address': '127.0.0.1:/TEST', - 'Snapshot Capacity (GB)': '10', - 'Snapshot Used (Bytes)': 1024, - 'Capacity (GB)': '20GB', - 'Target IP': '10.1.2.3', - 'Data Center': 'dal05', - 'Type': 'ENDURANCE', - 'ID': 100, - '# of Active Transactions': '1', - 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', - 'Replicant Count': '1', - 'Replication Status': 'Replicant Volume Provisioning ' - 'has completed.', - 'Replicant Volumes': [[ - {'Replicant ID': 'Volume Name', '1784': 'TEST_REP_1'}, - {'Replicant ID': 'Target IP', '1784': '10.3.174.79'}, - {'Replicant ID': 'Data Center', '1784': 'wdc01'}, - {'Replicant ID': 'Schedule', '1784': 'REPLICATION_HOURLY'}, - ], [ - {'Replicant ID': 'Volume Name', '1785': 'TEST_REP_2'}, - {'Replicant ID': 'Target IP', '1785': '10.3.177.84'}, - {'Replicant ID': 'Data Center', '1785': 'dal01'}, - {'Replicant ID': 'Schedule', '1785': 'REPLICATION_DAILY'}, - ]], - 'Original Volume Properties': [ - {'Property': 'Original Volume Size', - 'Value': '20'}, - {'Property': 'Original Volume Name', - 'Value': 'test-original-volume-name'}, - {'Property': 'Original Snapshot Name', - 'Value': 'test-original-snapshot-name'} - ] - }, json.loads(result.output)) + def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', From 77dc9368c0b13d0478a9e252e698ef83dcf5a3d1 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Wed, 26 Feb 2020 16:57:12 -0600 Subject: [PATCH 0270/1582] tox and style fixes --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/file.py | 2 +- SoftLayer/managers/storage.py | 3 --- tests/CLI/modules/block_tests.py | 4 ++-- tests/CLI/modules/file_tests.py | 5 ++--- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index eec22a4bb..4d129d07c 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -190,4 +190,4 @@ def _get_ids_from_username(self, username): results = self.list_block_volumes(username=username, mask=object_mask) if results: return [result['id'] for result in results] - + return [] diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 458ffa9aa..ce1b951c8 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -163,4 +163,4 @@ def _get_ids_from_username(self, username): results = self.list_file_volumes(username=username, mask=object_mask) if results: return [result['id'] for result in results] - + return [] diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 7c927362c..2d45bd0db 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -25,9 +25,6 @@ def __init__(self, client): self.client = client self.resolvers = [self._get_ids_from_username] - def _get_ids_from_username(self, username): - raise exceptions.SoftLayerError("Not Implemented.") - def get_volume_count_limits(self): """Returns a list of block volume count limit. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 9e5774400..f8408dcdd 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -107,10 +107,10 @@ def test_volume_detail_name_identifier(self): 'type': { 'type': {'operation': '!~ ISCSI'} } - }, + }, 'storageType': { 'keyName': {'operation': '*= BLOCK_STORAGE'} - }, + }, 'username': {'operation': '_= SL-12345'}}} self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index b37fb0c3f..20e065940 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -171,17 +171,16 @@ def test_volume_detail_name_identifier(self): 'type': { 'type': {'operation': '!~ NAS'} } - }, + }, 'storageType': { 'keyName': {'operation': '*= FILE_STORAGE'} - }, + }, 'username': {'operation': '_= SL-12345'}}} self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage', filter=expected_filter) self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=1) self.assert_no_fail(result) - def test_volume_order_performance_iops_not_given(self): result = self.run_command(['file', 'volume-order', '--storage-type=performance', '--size=20', From c64c058d9488c35f4d95e1ad9b5628d496334c70 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Feb 2020 14:31:41 -0600 Subject: [PATCH 0271/1582] a few minor unit test fixes --- tests/CLI/modules/block_tests.py | 4 +++- tests/managers/block_tests.py | 12 ++++++++++++ tests/managers/file_tests.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f8408dcdd..0dd7eac57 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -111,7 +111,9 @@ def test_volume_detail_name_identifier(self): 'storageType': { 'keyName': {'operation': '*= BLOCK_STORAGE'} }, - 'username': {'operation': '_= SL-12345'}}} + 'username': {'operation': '_= SL-12345'} + } + } self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) self.assert_called_with('SoftLayer_Network_Storage', 'getObject', identifier=100) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 30a767aaf..7febbfcf3 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1030,3 +1030,15 @@ def test_setCredentialPassword(self): def test_list_block_volume_limit(self): result = self.block.list_block_volume_limit() self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) + + def test_get_ids_from_username(self): + result = self.block._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') + self.assertEqual([100], result) + + def test_get_ids_from_username_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getIscsiNetworkStorage') + mock.return_value = [] + result = self.block._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') + self.assertEqual([], result) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index bdfe9aafd..dbd181228 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -879,3 +879,15 @@ def test_order_file_modified_endurance(self): def test_list_file_volume_limit(self): result = self.file.list_file_volume_limit() self.assertEqual(SoftLayer_Network_Storage.getVolumeCountLimits, result) + + def test_get_ids_from_username(self): + result = self.file._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') + self.assertEqual([1], result) + + def test_get_ids_from_username_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNasNetworkStorage') + mock.return_value = [] + result = self.file._get_ids_from_username("test") + self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') + self.assertEqual([], result) From 030bb5a91bede9df4e9ebdeca90c8e11b33243c7 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 09:09:03 -0600 Subject: [PATCH 0272/1582] minor --- SoftLayer/managers/block.py | 6 ++---- docs/cli/file.rst | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 8b379513f..d3c1918fe 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -623,13 +623,11 @@ def refresh_dep_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', 'refreshDependentDuplicate', - snapshot_id, id=volume_id) + return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an indepdent 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) diff --git a/docs/cli/file.rst b/docs/cli/file.rst index ad01b0337..13cf92a61 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -99,6 +99,14 @@ File Commands :prog: file volume-limits :show-nested: +.. click:: SoftLayer.CLI.file.refresh:cli + :prog file volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.file.convert:cli + :prog file volume-convert + :show-nested: + .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli :prog: file snapshot-schedule-list :show-nested: From 73b1315e2d4f732fb0ee0cc526aedca6526a0f81 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 10:58:50 -0600 Subject: [PATCH 0273/1582] UTs, and merge in coalesced storage branch --- .../fixtures/SoftLayer_Network_Storage.py | 9 +++++++++ SoftLayer/managers/storage.py | 15 ++++++++++++++ tests/CLI/modules/block_tests.py | 10 ++++++++++ tests/CLI/modules/file_tests.py | 10 ++++++++++ tests/managers/block_tests.py | 20 +++++++++++++++++++ tests/managers/file_tests.py | 20 +++++++++++++++++++ 6 files changed, 84 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 3c8d335e9..611e88005 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -157,6 +157,7 @@ 'storageTierLevel': 'READHEAVY_TIER', 'storageType': {'keyName': 'ENDURANCE_STORAGE'}, 'username': 'username', + 'dependentDuplicate': 1, } getSnapshots = [{ @@ -232,3 +233,11 @@ 'maximumAvailableCount': 300, 'provisionedCount': 100 } + +refreshDependentDuplicate = { + 'dependentDuplicate': 1 +} + +convertCloneDependentToIndependent = { + 'dependentDuplicate': 1 +} diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 2d45bd0db..a17322361 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -414,3 +414,18 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): immediate = True return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) + + def refresh_dep_dupe(self, volume_id, snapshot_id): + """"Refresh a dependent duplicate volume with a snapshot from its parent. + + :param integer volume_id: The id of the volume + :param integer snapshot_id: The id of the snapshot + """ + return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) + + def convert_dep_dupe(self, volume_id): + """Convert a dependent duplicate volume to an indepdent volume. + + :param integer volume_id: The id of the volume. + """ + return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 0dd7eac57..b39face10 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -716,3 +716,13 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) + + def test_dep_dupe_refresh(self): + result = self.run_command(['block', 'volume-refresh', '102', '103']) + + self.assert_no_fail(result) + + def test_dep_dupe_convert(self): + result = self.run_command(['block', 'volume-convert', '102']) + + self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 20e065940..1d64f54ae 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -695,3 +695,13 @@ def test_volume_limit(self, list_mock): }] result = self.run_command(['file', 'volume-limits']) self.assert_no_fail(result) + + def test_dep_dupe_refresh(self): + result = self.run_command(['file', 'volume-refresh', '102', '103']) + + self.assert_no_fail(result) + + def test_dep_dupe_convert(self): + result = self.run_command(['file', 'volume-convert', '102']) + + self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 7febbfcf3..c9731a04d 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1042,3 +1042,23 @@ def test_get_ids_from_username_empty(self): result = self.block._get_ids_from_username("test") self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) + + def test_refresh_block_depdupe(self): + result = self.block.refresh_dep_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'refreshDependentDuplicate', + identifier=123 + ) + + def test_convert_block_depdupe(self): + result = self.block.convert_dep_dupe(123) + self.assertEqual(SoftLayer_Network_Storage.convertCloneDependentToIndependent, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'convertCloneDependentToIndependent', + identifier=123 + ) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index dbd181228..6df2b8721 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -891,3 +891,23 @@ def test_get_ids_from_username_empty(self): result = self.file._get_ids_from_username("test") self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') self.assertEqual([], result) + + def test_refresh_file_depdupe(self): + result = self.file.refresh_dep_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'refreshDependentDuplicate', + identifier=123 + ) + + def test_convert_file_depdupe(self): + result = self.file.convert_dep_dupe(123) + self.assertEqual(SoftLayer_Network_Storage.convertCloneDependentToIndependent, result) + + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'convertCloneDependentToIndependent', + identifier=123 + ) From 833700ccbfc932f8c554bdf94399a1589d967d92 Mon Sep 17 00:00:00 2001 From: Ian Sutton Date: Fri, 28 Feb 2020 16:17:13 -0600 Subject: [PATCH 0274/1582] spelling --- SoftLayer/CLI/block/convert.py | 4 ++-- SoftLayer/CLI/file/convert.py | 4 ++-- SoftLayer/managers/storage.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/block/convert.py b/SoftLayer/CLI/block/convert.py index 795d3d27c..a48d8926c 100644 --- a/SoftLayer/CLI/block/convert.py +++ b/SoftLayer/CLI/block/convert.py @@ -1,4 +1,4 @@ -"""Convert a dependent duplicate volume to an indepdent volume.""" +"""Convert a dependent duplicate volume to an independent volume.""" # :license: MIT, see LICENSE for more details. import click @@ -10,7 +10,7 @@ @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): - """Convert a dependent duplicate volume to an indepdent volume.""" + """Convert a dependent duplicate volume to an independent volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.convert_dep_dupe(volume_id) diff --git a/SoftLayer/CLI/file/convert.py b/SoftLayer/CLI/file/convert.py index 7c01d8c53..8558ef009 100644 --- a/SoftLayer/CLI/file/convert.py +++ b/SoftLayer/CLI/file/convert.py @@ -1,4 +1,4 @@ -"""Convert a dependent duplicate volume to an indepdent volume.""" +"""Convert a dependent duplicate volume to an independent volume.""" # :license: MIT, see LICENSE for more details. import click @@ -10,7 +10,7 @@ @click.argument('volume_id') @environment.pass_env def cli(env, volume_id): - """Convert a dependent duplicate volume to an indepdent volume.""" + """Convert a dependent duplicate volume to an independent volume.""" file_manager = SoftLayer.FileStorageManager(env.client) resp = file_manager.convert_dep_dupe(volume_id) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index a17322361..9a8015d58 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -424,7 +424,7 @@ def refresh_dep_dupe(self, volume_id, snapshot_id): return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) def convert_dep_dupe(self, volume_id): - """Convert a dependent duplicate volume to an indepdent volume. + """Convert a dependent duplicate volume to an independent volume. :param integer volume_id: The id of the volume. """ From fc787a0c077e12a57325b21df9c7c02c0b255090 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 9 Mar 2020 15:20:59 -0500 Subject: [PATCH 0275/1582] #801 added support for json filters, and json parsing for parameters --- SoftLayer/CLI/call_api.py | 59 +++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index 6e16a2a77..c1f3f3191 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -1,5 +1,6 @@ """Call arbitrary API endpoints.""" import click +import json from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions @@ -27,8 +28,7 @@ def _build_filters(_filters): if len(top_parts) == 2: break else: - raise exceptions.CLIAbort('Failed to find valid operation for: %s' - % _filter) + raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) key, value = top_parts current = root @@ -67,26 +67,59 @@ def _build_python_example(args, kwargs): return call_str +def _validate_filter(ctx, param, value): + """Validates a JSON style object filter""" + _filter = None + + if value: + try: + _filter = json.loads(value) + if not isinstance(_filter, dict): + raise exceptions.CLIAbort("\"{}\" should be a JSON object, but is a {} instead.". + format(_filter, type(_filter))) + except json.JSONDecodeError as e: + raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, e)) + + return _filter + +def _validate_parameters(ctx, param, value): + """Checks if value is a JSON string, and converts it to a datastructure if that is true""" + + validated_values = [] + for i, parameter in enumerate(value): + if isinstance(parameter, str): + # looks like a JSON string... + if '{' in parameter or '[' in parameter: + try: + parameter = json.loads(parameter) + except json.JSONDecodeError as e: + click.secho("{} looked like json, but wasn't valid, passing to API as is. {}".format(parameter, e), + fg='red') + validated_values.append(parameter) + return validated_values @click.command('call', short_help="Call arbitrary API endpoints.") @click.argument('service') @click.argument('method') -@click.argument('parameters', nargs=-1) +@click.argument('parameters', nargs=-1, callback=_validate_parameters) @click.option('--id', '_id', help="Init parameter") @helpers.multi_option('--filter', '-f', '_filters', - help="Object filters. This should be of the form: " - "'property=value' or 'nested.property=value'. Complex " - "filters like betweenDate are not currently supported.") + help="Object filters. This should be of the form: 'property=value' or 'nested.property=value'." + "Complex filters should use --json-filter.") @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") +@click.option('--json-filter', callback=_validate_filter, + help="A JSON string to be passed in as the object filter to the API call." + "Remember to use double quotes (\") for variable names. Can NOT be used with --filter.") @environment.pass_env def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, - output_python=False): + output_python=False, json_filter=None): """Call arbitrary API endpoints with the given SERVICE and METHOD. + For parameters that require a datatype, use a JSON string for that parameter. Example:: slcli call-api Account getObject @@ -100,12 +133,22 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, --mask=id,hostname,datacenter.name,maxCpu slcli call-api Account getVirtualGuests \\ -f 'virtualGuests.datacenter.name IN dal05,sng01' + slcli call-api Account getVirtualGuests \\ + --json-filter '{"virtualGuests":{"hostname": {"operation": "^= test"}}}' --limit=10 + slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ + '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ + if _filters and json_filter: + raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") + + object_filter = _build_filters(_filters) + if json_filter: + object_filter.update(json_filter) args = [service, method] + list(parameters) kwargs = { 'id': _id, - 'filter': _build_filters(_filters), + 'filter': object_filter, 'mask': mask, 'limit': limit, 'offset': offset, From f87671be97213be9769c882fc990eed71a83ed53 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 9 Mar 2020 18:50:05 -0400 Subject: [PATCH 0276/1582] Fix order place bare metal capacity restriction. --- SoftLayer/managers/ordering.py | 7 ++++++- tests/managers/ordering_tests.py | 33 ++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e86679f61..7e4e81db5 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -351,7 +351,7 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): keynames in the given package """ - mask = 'id, capacity, itemCategory, keyName, prices[categories]' + mask = 'id, description, capacity, itemCategory, keyName, prices[categories]' items = self.list_items(package_keyname, mask=mask) item_capacity = self.get_item_capacity(items, item_keynames) @@ -422,6 +422,11 @@ def get_item_capacity(self, items, item_keynames): if "TIER" in item["keyName"]: item_capacity = item['capacity'] break + if "INTEL" in item["keyName"]: + item_split = item['description'].split("(") + item_core = item_split[1].split(" ") + item_capacity = item_core[0] + break return item_capacity def get_preset_prices(self, preset): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 045517cd8..c8a3870ae 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -309,7 +309,7 @@ def test_get_price_id_list(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -327,7 +327,7 @@ def test_get_price_id_list_no_core(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], None) - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -342,7 +342,7 @@ def test_get_price_id_list_item_not_found(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.get_price_id_list, 'PACKAGE_KEYNAME', ['ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual("Item ITEM2 does not exist for package PACKAGE_KEYNAME", str(exc)) @@ -357,7 +357,7 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) @@ -608,7 +608,7 @@ def test_location_group_id_none(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -626,7 +626,7 @@ def test_location_groud_id_empty(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM2'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, capacity, itemCategory, keyName, ' + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' 'prices[categories]') self.assertEqual([price1['id'], price2['id']], prices) @@ -673,11 +673,10 @@ def test_issues1067(self): { 'id': 10453, 'itemCategory': {'categoryCode': 'server'}, + "description": "Dual Intel Xeon Silver 4110 (16 Cores, 2.10 GHz)", 'keyName': 'INTEL_INTEL_XEON_4110_2_10', 'prices': [ { - 'capacityRestrictionMaximum': '2', - 'capacityRestrictionMinimum': '2', 'capacityRestrictionType': 'PROCESSOR', 'categories': [{'categoryCode': 'os'}], 'id': 201161, @@ -744,3 +743,21 @@ def test_get_item_capacity_storage(self): item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) self.assertEqual(1, int(item_capacity)) + + def test_get_item_capacity_intel(self): + + items = [{ + "capacity": "1", + "id": 6131, + "description": "Dual Intel Xeon E5-2690 v3 (24 Cores, 2.60 GHz)", + "keyName": "INTEL_XEON_2690_2_60", + }, + { + "capacity": "1", + "id": 10201, + "keyName": "GUEST_CORE_1_DEDICATED", + }] + + item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) + + self.assertEqual(24, int(item_capacity)) From bc787e4b7d351632091860111f73d42175d886ac Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 9 Mar 2020 19:06:04 -0400 Subject: [PATCH 0277/1582] Fix tox analysis 122 > 120 characters. --- tests/managers/ordering_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index c8a3870ae..39caa72f4 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -357,8 +357,8 @@ def test_get_price_id_list_gpu_items_with_two_categories(self): prices = self.ordering.get_price_id_list('PACKAGE_KEYNAME', ['ITEM1', 'ITEM1'], "8") - list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, keyName, ' - 'prices[categories]') + list_mock.assert_called_once_with('PACKAGE_KEYNAME', mask='id, description, capacity, itemCategory, ' + 'keyName, ' 'prices[categories]') self.assertEqual([price2['id'], price1['id']], prices) def test_generate_no_complex_type(self): From fb10a3586c3232fe35dcae9208b280526382236b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Mar 2020 15:25:29 -0500 Subject: [PATCH 0278/1582] tox fixes for advanced filters --- SoftLayer/CLI/call_api.py | 27 +++++++++------ docs/cli/commands.rst | 8 +++++ tests/CLI/modules/call_api_tests.py | 54 +++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index c1f3f3191..7eccfd37f 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -1,7 +1,8 @@ """Call arbitrary API endpoints.""" -import click import json +import click + from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -28,7 +29,7 @@ def _build_filters(_filters): if len(top_parts) == 2: break else: - raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) + raise exceptions.CLIAbort('Failed to find valid operation for: %s' % _filter) key, value = top_parts current = root @@ -67,37 +68,40 @@ def _build_python_example(args, kwargs): return call_str -def _validate_filter(ctx, param, value): + +def _validate_filter(ctx, param, value): # pylint: disable=unused-argument """Validates a JSON style object filter""" _filter = None - + # print("VALUE: {}".format(value)) if value: try: _filter = json.loads(value) if not isinstance(_filter, dict): raise exceptions.CLIAbort("\"{}\" should be a JSON object, but is a {} instead.". format(_filter, type(_filter))) - except json.JSONDecodeError as e: - raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, e)) + except json.JSONDecodeError as error: + raise exceptions.CLIAbort("\"{}\" is not valid JSON. {}".format(value, error)) return _filter -def _validate_parameters(ctx, param, value): + +def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument """Checks if value is a JSON string, and converts it to a datastructure if that is true""" validated_values = [] - for i, parameter in enumerate(value): + for parameter in value: if isinstance(parameter, str): # looks like a JSON string... if '{' in parameter or '[' in parameter: try: parameter = json.loads(parameter) - except json.JSONDecodeError as e: - click.secho("{} looked like json, but wasn't valid, passing to API as is. {}".format(parameter, e), - fg='red') + except json.JSONDecodeError as error: + click.secho("{} looked like json, but was invalid, passing to API as is. {}". + format(parameter, error), fg='red') validated_values.append(parameter) return validated_values + @click.command('call', short_help="Call arbitrary API endpoints.") @click.argument('service') @click.argument('method') @@ -138,6 +142,7 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ + if _filters and json_filter: raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") diff --git a/docs/cli/commands.rst b/docs/cli/commands.rst index a577adcdb..c1c5bd3fb 100644 --- a/docs/cli/commands.rst +++ b/docs/cli/commands.rst @@ -3,6 +3,14 @@ Call API ======== +This function allows you to easily call any API. The format is + +`slcli call-api SoftLayer_Service method param1 param2 --id=1234 --mask="mask[id,name]"` + +Parameters should be in the order they are presented on sldn.softlayer.com. +Any complex parameters (those that link to other datatypes) should be presented as JSON strings. They need to be enclosed in single quotes (`'`), and variables and strings enclosed in double quotes (`"`). + +For example: `{"hostname":"test",ssh_keys:[{"id":1234}]}` .. click:: SoftLayer.CLI.call_api:cli :prog: call-api diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index d99c59d35..8d3f19ab2 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -158,8 +158,7 @@ def test_object_table(self): 'None': None, 'Bool': True} - result = self.run_command(['call-api', 'Service', 'method'], - fmt='table') + result = self.run_command(['call-api', 'Service', 'method'], fmt='table') self.assert_no_fail(result) # NOTE(kmcdonald): Order is not guaranteed @@ -179,8 +178,7 @@ def test_object_nested(self): result = self.run_command(['call-api', 'Service', 'method']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - {'this': {'is': [{'pretty': 'nested'}]}}) + self.assertEqual(json.loads(result.output), {'this': {'is': [{'pretty': 'nested'}]}}) def test_list(self): mock = self.set_mock('SoftLayer_Service', 'method') @@ -208,8 +206,7 @@ def test_list_table(self): 'None': None, 'Bool': True}] - result = self.run_command(['call-api', 'Service', 'method'], - fmt='table') + result = self.run_command(['call-api', 'Service', 'method'], fmt='table') self.assert_no_fail(result) self.assertEqual(result.output, @@ -224,12 +221,10 @@ def test_parameters(self): mock = self.set_mock('SoftLayer_Service', 'method') mock.return_value = {} - result = self.run_command(['call-api', 'Service', 'method', - 'arg1', '1234']) + result = self.run_command(['call-api', 'Service', 'method', 'arg1', '1234']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Service', 'method', - args=('arg1', '1234')) + self.assert_called_with('SoftLayer_Service', 'method', args=('arg1', '1234')) def test_fixture_not_implemented(self): service = 'SoftLayer_Test' @@ -264,3 +259,42 @@ def test_fixture_exception(self): self.assertIsInstance(result.exception, SoftLayerAPIError) output = '%s::%s fixture is not implemented' % (call_service, call_method) self.assertIn(output, result.exception.faultString) + + def test_json_filter_validation(self): + json_filter = '{"test":"something"}' + result = call_api._validate_filter(None, None, json_filter) + self.assertEqual(result['test'], 'something') + + # Valid JSON, but we expect objects, not simple types + with pytest.raises(exceptions.CLIAbort): + call_api._validate_filter(None, None, '"test"') + + # Invalid JSON + with pytest.raises(exceptions.CLIAbort): + call_api._validate_filter(None, None, 'test') + + # Empty Request + result = call_api._validate_filter(None, None, None) + self.assertEqual(None, result) + + def test_json_parameters_validation(self): + json_params = ('{"test":"something"}', 'String', 1234, '[{"a":"b"}]', '{funky non [ Json') + result = call_api._validate_parameters(None, None, json_params) + self.assertEqual(result[0], {"test": "something"}) + self.assertEqual(result[1], "String") + self.assertEqual(result[2], 1234) + self.assertEqual(result[3], [{"a": "b"}]) + self.assertEqual(result[4], "{funky non [ Json") + + def test_filter_with_filter(self): + result = self.run_command(['call-api', 'Account', 'getObject', '--filter=nested.property=5432', + '--json-filter={"test":"something"}']) + self.assertEqual(2, result.exit_code) + self.assertEqual(result.exception.message, "--filter and --json-filter cannot be used together.") + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_json_filter(self): + pass + result = self.run_command(['call-api', 'Account', 'getObject', '--json-filter={"test":"something"}']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject', filter={"test": "something"}) From 66d85aa47bab47f4e8436a2e18381ff0355fa54d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 11 Mar 2020 15:37:49 -0500 Subject: [PATCH 0279/1582] removed debug message --- SoftLayer/CLI/call_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index 7eccfd37f..c25724076 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -72,7 +72,6 @@ def _build_python_example(args, kwargs): def _validate_filter(ctx, param, value): # pylint: disable=unused-argument """Validates a JSON style object filter""" _filter = None - # print("VALUE: {}".format(value)) if value: try: _filter = json.loads(value) From 8d2901ddf39bab78f86e2b11bee7ee9652169ff5 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 13 Mar 2020 18:29:50 -0400 Subject: [PATCH 0280/1582] add image datacenter sub command --- SoftLayer/CLI/image/datacenter.py | 30 +++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/image.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 SoftLayer/CLI/image/datacenter.py diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py new file mode 100644 index 000000000..44232e358 --- /dev/null +++ b/SoftLayer/CLI/image/datacenter.py @@ -0,0 +1,30 @@ +"""Edit details of an image.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--add/--remove', + default=False, + help="To add or remove Datacenter") +@click.argument('locations', nargs=-1) +@environment.pass_env +def cli(env, identifier, add, locations): + """Add/Remove datacenter of an image.""" + + image_mgr = SoftLayer.ImageManager(env.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'Image template') + + if add: + result = image_mgr.add_locations(image_id, locations) + else: + result = image_mgr.remove_locations(image_id, locations) + + env.fout(result) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b908be4f5..f6edee475 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -163,6 +163,7 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), + ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 34efb36bf..66efaba74 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -7,6 +7,7 @@ """ from SoftLayer import utils +from SoftLayer.CLI import exceptions IMAGE_MASK = ('id,accountId,name,globalIdentifier,blockDevices,parentId,' 'createDate,transaction') @@ -187,3 +188,56 @@ def export_image_to_uri(self, image_id, uri, ibm_api_key=None): }, id=image_id) else: return self.vgbdtg.copyToExternalSource({'uri': uri}, id=image_id) + + def add_locations(self, image_id, location_names): + """Add available locations to an archive image template. + + :param int image_id: The ID of the image + :param location_names: Locations for the Image. + """ + locations = self.get_locations_id_list(image_id, location_names) + locations_ids = [{'id': location_id} for location_id in locations] + return self.vgbdtg.addLocations(locations_ids, id=image_id) + + def remove_locations(self, image_id, location_names): + """Remove available locations from an archive image template. + + :param int image_id: The ID of the image + :param location_names: Locations for the Image. + """ + locations = self.get_locations_id_list(image_id, location_names) + locations_ids = [{'id': location_id} for location_id in locations] + return self.vgbdtg.removeLocations(locations_ids, id=image_id) + + def get_storage_locations(self, image_id): + """Get available locations for public image storage. + + :param int image_id: The ID of the image + """ + return self.vgbdtg.getStorageLocations(id=image_id) + + def get_locations_id_list(self, image_id, location_names): + """Converts a list of location names to a list of location IDs. + + :param int image_id: The ID of the image. + :param list location_names: A list of location names strings. + :returns: A list of locations IDs associated with the given location + keynames in the image id. + """ + locations = self.get_storage_locations(image_id) + locations_ids = [] + matching_location = {} + + for location_name in location_names: + try: + for location in locations: + if location_name == location.get('name'): + matching_location = location + break + except IndexError: + raise exceptions.SoftLayerError( + "Location {} does not exist for available locations for image {}".format(location_name, + image_id)) + locations_ids.append(matching_location.get('id')) + + return locations_ids From f571572729b888ce2d5a320d59c9ab752970b44d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Mar 2020 15:57:26 -0500 Subject: [PATCH 0281/1582] updated docblock for call-api --- SoftLayer/CLI/call_api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index c25724076..cbce4eccb 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -115,8 +115,9 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, - help="A JSON string to be passed in as the object filter to the API call." - "Remember to use double quotes (\") for variable names. Can NOT be used with --filter.") + help="A JSON string to be passed in as the object filter to the API call. " + "Remember to use double quotes (\") for variable names. Can NOT be used with --filter. " + "Dont use whitespace outside of strings, or the slcli might have trouble parsing it.") @environment.pass_env def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, output_python=False, json_filter=None): @@ -137,7 +138,7 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, slcli call-api Account getVirtualGuests \\ -f 'virtualGuests.datacenter.name IN dal05,sng01' slcli call-api Account getVirtualGuests \\ - --json-filter '{"virtualGuests":{"hostname": {"operation": "^= test"}}}' --limit=10 + --json-filter '{"virtualGuests":{"hostname":{"operation":"^= test"}}}' --limit=10 slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' """ From cd8ce76db6eb69868686952d287334b1fd77d045 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 17 Mar 2020 16:05:09 -0500 Subject: [PATCH 0282/1582] #1243 refactored unit tests to use python unittest over testtools, because testtools was breaking in the latest version --- SoftLayer/CLI/deprecated.py | 14 -------------- SoftLayer/testing/__init__.py | 25 +++++++++++++++++-------- tests/CLI/deprecated_tests.py | 33 --------------------------------- 3 files changed, 17 insertions(+), 55 deletions(-) delete mode 100644 SoftLayer/CLI/deprecated.py delete mode 100644 tests/CLI/deprecated_tests.py diff --git a/SoftLayer/CLI/deprecated.py b/SoftLayer/CLI/deprecated.py deleted file mode 100644 index 0609a9246..000000000 --- a/SoftLayer/CLI/deprecated.py +++ /dev/null @@ -1,14 +0,0 @@ -""" - SoftLayer.CLI.deprecated - ~~~~~~~~~~~~~~~~~~~~~~~~ - Handles usage of the deprecated command name, 'sl'. - :license: MIT, see LICENSE for more details. -""" -import sys - - -def main(): - """Main function for the deprecated 'sl' command.""" - print("ERROR: Use the 'slcli' command instead.", file=sys.stderr) - print("> slcli %s" % ' '.join(sys.argv[1:]), file=sys.stderr) - sys.exit(-1) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index d02e60f0e..9c8b81c47 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -8,10 +8,10 @@ # pylint: disable=invalid-name import logging import os.path +import unittest from click import testing import mock -import testtools import SoftLayer from SoftLayer.CLI import core @@ -68,8 +68,7 @@ def _record_call(self, call): 'offset']: details.append('%s=%r' % (prop, getattr(call, prop))) - logging.info('%s::%s called; %s', - call.service, call.method, '; '.join(details)) + logging.info('%s::%s called; %s', call.service, call.method, '; '.join(details)) def _mock_key(service, method): @@ -77,7 +76,7 @@ def _mock_key(service, method): return '%s::%s' % (service, method) -class TestCase(testtools.TestCase): +class TestCase(unittest.TestCase): """Testcase class with PEP-8 compatible method names.""" @classmethod @@ -100,7 +99,7 @@ def tear_down(self): """Aliased from tearDown.""" def setUp(self): # NOQA - testtools.TestCase.setUp(self) + unittest.TestCase.setUp(self) self.mocks.clear() @@ -114,7 +113,7 @@ def setUp(self): # NOQA self.set_up() def tearDown(self): # NOQA - testtools.TestCase.tearDown(self) + super(TestCase, self).tearDown() self.tear_down() self.mocks.clear() @@ -141,8 +140,7 @@ def assert_called_with(self, service, method, **props): if self.calls(service, method, **props): return - raise AssertionError('%s::%s was not called with given properties: %s' - % (service, method, props)) + raise AssertionError('%s::%s was not called with given properties: %s' % (service, method, props)) def assert_no_fail(self, result): """Fail when a failing click result has an error""" @@ -170,6 +168,17 @@ def run_command(self, args=None, env=None, fixtures=True, fmt='json', stdin=None runner = testing.CliRunner() return runner.invoke(core.cli, args=args, input=stdin, obj=env or self.env) + def assertRaises(self, exception, function_callable, *args, **kwds): # pylint: disable=arguments-differ + """Converts testtools.assertRaises to unittest.assertRaises calls. + + testtools==2.4.0 require unittest2, which breaks pytest>=5.4.1 on skipTest. + But switching to just using unittest breaks assertRaises because the format is slightly different. + This basically just reformats the call so I don't have to re-write a bunch of tests. + """ + with super(TestCase, self).assertRaises(exception) as cm: + function_callable(*args, **kwds) + return cm.exception + def call_has_props(call, props): """Check if a call has matching properties of a given props dictionary.""" diff --git a/tests/CLI/deprecated_tests.py b/tests/CLI/deprecated_tests.py deleted file mode 100644 index f28025f36..000000000 --- a/tests/CLI/deprecated_tests.py +++ /dev/null @@ -1,33 +0,0 @@ -""" - SoftLayer.tests.CLI.deprecated_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -import io - -import mock - -from SoftLayer.CLI import deprecated -from SoftLayer import testing - - -class EnvironmentTests(testing.TestCase): - - def test_main(self): - - with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: - ex = self.assertRaises(SystemExit, deprecated.main) - self.assertEqual(ex.code, -1) - - self.assertIn("ERROR: Use the 'slcli' command instead.", - fake_out.getvalue()) - - def test_with_args(self): - with mock.patch('sys.stderr', new=io.StringIO()) as fake_out: - with mock.patch('sys.argv', new=['sl', 'module', 'subcommand']): - ex = self.assertRaises(SystemExit, deprecated.main) - self.assertEqual(ex.code, -1) - - self.assertIn("ERROR: Use the 'slcli' command instead.", - fake_out.getvalue()) From 9a4dc533cedbb79cb8ffcbf3ecd3226119631975 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 17 Mar 2020 18:20:14 -0400 Subject: [PATCH 0283/1582] Adding image test --- SoftLayer/CLI/image/datacenter.py | 3 +- ...rtual_Guest_Block_Device_Template_Group.py | 18 ++++-- tests/CLI/modules/image_tests.py | 56 +++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 tests/CLI/modules/image_tests.py diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index 44232e358..66d1f0ed0 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -5,7 +5,6 @@ import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import helpers @@ -20,7 +19,7 @@ def cli(env, identifier, add, locations): """Add/Remove datacenter of an image.""" image_mgr = SoftLayer.ImageManager(env.client) - image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'Image template') + image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') if add: result = image_mgr.add_locations(image_id, locations) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py index 785ed3b05..82956c0a2 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py @@ -7,6 +7,11 @@ 'name': 'test_image', 'parentId': '', 'publicFlag': True, + 'children': [{ + 'datacenter': { + 'name': 'ams01' + } + }], }, { 'accountId': 1234, 'blockDevices': [], @@ -16,6 +21,11 @@ 'name': 'test_image2', 'parentId': '', 'publicFlag': True, + 'children': [{ + 'datacenter': { + 'name': 'ams01' + } + }], }] getObject = IMAGES[0] @@ -23,17 +33,17 @@ deleteObject = {} editObject = True setTags = True -createFromExternalSource = [{ +createFromExternalSource = { 'createDate': '2013-12-05T21:53:03-06:00', 'globalIdentifier': '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', 'id': 100, 'name': 'test_image', -}] -createFromIcos = [{ +} +createFromIcos = { 'createDate': '2013-12-05T21:53:03-06:00', 'globalIdentifier': '0B5DEAF4-643D-46CA-A695-CECBE8832C9D', 'id': 100, 'name': 'test_image', -}] +} copyToExternalSource = True copyToIcos = True diff --git a/tests/CLI/modules/image_tests.py b/tests/CLI/modules/image_tests.py new file mode 100644 index 000000000..e100d5eab --- /dev/null +++ b/tests/CLI/modules/image_tests.py @@ -0,0 +1,56 @@ +""" + SoftLayer.tests.CLI.modules.image_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import json +import os.path + +import mock + +from SoftLayer.CLI import exceptions +from SoftLayer import testing + + +class ImageTests(testing.TestCase): + + def test_detail(self): + result = self.run_command(['image', 'detail', '100']) + self.assert_no_fail(result) + + def test_delete(self): + result = self.run_command(['image', 'delete', '100']) + self.assert_no_fail(result) + + def test_edit_note(self): + result = self.run_command(['image', 'edit', '100', '--note=test']) + self.assert_no_fail(result) + + def test_edit_name(self): + result = self.run_command(['image', 'edit', '100', '--name=test']) + self.assert_no_fail(result) + + def test_edit_tag(self): + result = self.run_command(['image', 'edit', '100', '--tag=test']) + self.assert_no_fail(result) + + def test_import(self): + result = self.run_command(['image', 'import', '100', 'swift://test']) + self.assert_no_fail(result) + + def test_export(self): + result = self.run_command(['image', 'export', '100', 'swift://test']) + self.assert_no_fail(result) + + def test_list(self): + result = self.run_command(['image', 'list']) + self.assert_no_fail(result) + + def test_datacenter_add(self): + result = self.run_command(['image', 'datacenter', '100', '--add', 'test']) + self.assert_no_fail(result) + + # def test_datacenter_remove(self): + # result = self.run_command(['image', 'datacenter', '--remove', 'test']) + # self.assert_no_fail(result) From 8c61232f38c1412b21f2c47dc416e32cc5138da8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 20 Mar 2020 15:55:52 -0400 Subject: [PATCH 0284/1582] add tests for image manager and cli --- SoftLayer/CLI/image/datacenter.py | 2 +- ...rtual_Guest_Block_Device_Template_Group.py | 6 ++++ SoftLayer/managers/image.py | 19 ++++++------ tests/CLI/modules/image_tests.py | 17 +++++----- tests/managers/image_tests.py | 31 +++++++++++++++++++ 5 files changed, 56 insertions(+), 19 deletions(-) diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index 66d1f0ed0..fd59ce271 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -13,7 +13,7 @@ @click.option('--add/--remove', default=False, help="To add or remove Datacenter") -@click.argument('locations', nargs=-1) +@click.argument('locations', nargs=-1, required=True) @environment.pass_env def cli(env, identifier, add, locations): """Add/Remove datacenter of an image.""" diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py index 82956c0a2..e2e7f0e45 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest_Block_Device_Template_Group.py @@ -47,3 +47,9 @@ } copyToExternalSource = True copyToIcos = True +addLocations = True +removeLocations = True +getStorageLocations = [ + {'id': 265592, 'longName': 'Amsterdam 1', 'name': 'ams01', 'statusId': 2}, + {'id': 814994, 'longName': 'Amsterdam 3', 'name': 'ams03', 'statusId': 2}, +] diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 66efaba74..8a62a625b 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -6,8 +6,8 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import exceptions from SoftLayer import utils -from SoftLayer.CLI import exceptions IMAGE_MASK = ('id,accountId,name,globalIdentifier,blockDevices,parentId,' 'createDate,transaction') @@ -227,17 +227,18 @@ def get_locations_id_list(self, image_id, location_names): locations = self.get_storage_locations(image_id) locations_ids = [] matching_location = {} + output_error = "Location {} does not exist for available locations for image {}" for location_name in location_names: - try: - for location in locations: - if location_name == location.get('name'): - matching_location = location - break - except IndexError: + for location in locations: + if location_name == location.get('name'): + matching_location = location + break + if matching_location.get('id') is None: raise exceptions.SoftLayerError( - "Location {} does not exist for available locations for image {}".format(location_name, - image_id)) + output_error.format(location_name, image_id) + ) + locations_ids.append(matching_location.get('id')) return locations_ids diff --git a/tests/CLI/modules/image_tests.py b/tests/CLI/modules/image_tests.py index e100d5eab..9305cc4ba 100644 --- a/tests/CLI/modules/image_tests.py +++ b/tests/CLI/modules/image_tests.py @@ -4,12 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import json -import os.path -import mock - -from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -48,9 +43,13 @@ def test_list(self): self.assert_no_fail(result) def test_datacenter_add(self): - result = self.run_command(['image', 'datacenter', '100', '--add', 'test']) + result = self.run_command(['image', 'datacenter', '100', '--add', 'ams01']) + self.assert_no_fail(result) + + def test_datacenter_remove(self): + result = self.run_command(['image', 'datacenter', '100', '--remove', 'ams01']) self.assert_no_fail(result) - # def test_datacenter_remove(self): - # result = self.run_command(['image', 'datacenter', '--remove', 'test']) - # self.assert_no_fail(result) + def test_datacenter_remove_fails(self): + result = self.run_command(['image', 'datacenter', '100', '--remove']) + self.assertEqual(2, result.exit_code) diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index b36deea75..6f88689b4 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -6,6 +6,7 @@ """ import SoftLayer +from SoftLayer import exceptions from SoftLayer import testing IMAGE_SERVICE = 'SoftLayer_Virtual_Guest_Block_Device_Template_Group' @@ -192,3 +193,33 @@ def test_export_image_cos(self): 'copyToIcos', args=({'uri': 'cos://someuri', 'ibmApiKey': 'someApiKey'},), identifier=1234) + + def test_add_locations_image(self): + locations = ['ams01'] + self.image.add_locations(100, locations) + + self.assert_called_with(IMAGE_SERVICE, 'addLocations', identifier=100) + + def test_add_locations_fail(self): + locations = ['test'] + self.assertRaises( + exceptions.SoftLayerError, + self.image.add_locations, + 100, + locations + ) + + def test_remove_locations_image(self): + locations = ['ams01'] + self.image.remove_locations(100, locations) + + self.assert_called_with(IMAGE_SERVICE, 'removeLocations', identifier=100) + + def test_get_locations_id_fails(self): + locations = ['test'] + self.assertRaises( + exceptions.SoftLayerError, + self.image.get_locations_id_list, + 100, + locations + ) From 953ce15f2a3b2ffdc0e27d95afbe4f8cda2cdbfd Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 23 Mar 2020 17:18:30 -0400 Subject: [PATCH 0285/1582] set default behavior to add datacenters --- SoftLayer/CLI/image/datacenter.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/image/datacenter.py b/SoftLayer/CLI/image/datacenter.py index fd59ce271..8d59c6dbe 100644 --- a/SoftLayer/CLI/image/datacenter.py +++ b/SoftLayer/CLI/image/datacenter.py @@ -10,8 +10,7 @@ @click.command() @click.argument('identifier') -@click.option('--add/--remove', - default=False, +@click.option('--add/--remove', default=True, help="To add or remove Datacenter") @click.argument('locations', nargs=-1, required=True) @environment.pass_env From 212472ffc4667f535755bea2d0e69a276f038888 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 23 Mar 2020 18:36:44 -0400 Subject: [PATCH 0286/1582] improve gather list of locations --- SoftLayer/managers/image.py | 23 +++++++++-------------- tests/managers/image_tests.py | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index 8a62a625b..d30b05305 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -195,9 +195,8 @@ def add_locations(self, image_id, location_names): :param int image_id: The ID of the image :param location_names: Locations for the Image. """ - locations = self.get_locations_id_list(image_id, location_names) - locations_ids = [{'id': location_id} for location_id in locations] - return self.vgbdtg.addLocations(locations_ids, id=image_id) + locations = self.get_locations_list(image_id, location_names) + return self.vgbdtg.addLocations(locations, id=image_id) def remove_locations(self, image_id, location_names): """Remove available locations from an archive image template. @@ -205,9 +204,8 @@ def remove_locations(self, image_id, location_names): :param int image_id: The ID of the image :param location_names: Locations for the Image. """ - locations = self.get_locations_id_list(image_id, location_names) - locations_ids = [{'id': location_id} for location_id in locations] - return self.vgbdtg.removeLocations(locations_ids, id=image_id) + locations = self.get_locations_list(image_id, location_names) + return self.vgbdtg.removeLocations(locations, id=image_id) def get_storage_locations(self, image_id): """Get available locations for public image storage. @@ -216,13 +214,12 @@ def get_storage_locations(self, image_id): """ return self.vgbdtg.getStorageLocations(id=image_id) - def get_locations_id_list(self, image_id, location_names): - """Converts a list of location names to a list of location IDs. + def get_locations_list(self, image_id, location_names): + """Converts a list of location names to a list of locations. :param int image_id: The ID of the image. :param list location_names: A list of location names strings. - :returns: A list of locations IDs associated with the given location - keynames in the image id. + :returns: A list of locations associated with the given location names in the image. """ locations = self.get_storage_locations(image_id) locations_ids = [] @@ -235,10 +232,8 @@ def get_locations_id_list(self, image_id, location_names): matching_location = location break if matching_location.get('id') is None: - raise exceptions.SoftLayerError( - output_error.format(location_name, image_id) - ) + raise exceptions.SoftLayerError(output_error.format(location_name, image_id)) - locations_ids.append(matching_location.get('id')) + locations_ids.append(matching_location) return locations_ids diff --git a/tests/managers/image_tests.py b/tests/managers/image_tests.py index 6f88689b4..e8d585aca 100644 --- a/tests/managers/image_tests.py +++ b/tests/managers/image_tests.py @@ -219,7 +219,7 @@ def test_get_locations_id_fails(self): locations = ['test'] self.assertRaises( exceptions.SoftLayerError, - self.image.get_locations_id_list, + self.image.get_locations_list, 100, locations ) From c1f55e73366da4d6f029cccd96cd282cce422faf Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 24 Mar 2020 09:15:53 -0400 Subject: [PATCH 0287/1582] fix the issue 887 --- SoftLayer/CLI/ticket/__init__.py | 8 ++- SoftLayer/CLI/ticket/create.py | 2 +- SoftLayer/CLI/ticket/detail.py | 5 +- tests/CLI/modules/ticket_tests.py | 107 +++++------------------------- 4 files changed, 25 insertions(+), 97 deletions(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index 9886aa686..b08663322 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -1,10 +1,10 @@ """Support tickets.""" import click +import re from SoftLayer.CLI import formatting - TEMPLATE_MSG = "***** SoftLayer Ticket Content ******" # https://softlayer.github.io/reference/services/SoftLayer_Ticket_Priority/getPriorities/ @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json, update_count=1): """Get output about a ticket. :param integer id: the ticket ID @@ -64,6 +64,8 @@ def get_ticket_results(mgr, ticket_id, update_count=1): # NOTE(kmcdonald): Windows new-line characters need to be stripped out wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) + if is_json: + if '\n' in wrapped_entry: + wrapped_entry = re.sub(r"(? Date: Tue, 24 Mar 2020 09:50:45 -0400 Subject: [PATCH 0288/1582] fix the coverage test --- tests/CLI/modules/ticket_tests.py | 97 ++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 46d957f81..dcfa44968 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -1,13 +1,15 @@ """ SoftLayer.tests.CLI.modules.ticket_tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - :license: MIT, see LICENSE for more details. """ import json import mock from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import ticket +from SoftLayer.managers import TicketManager from SoftLayer import testing @@ -40,8 +42,8 @@ def test_detail(self): 'status': 'Closed', 'title': 'Cloud Instance Cancellation - 08/01/13', 'update 1': 'a bot says something', - 'update 2': 'By John Smith user says something', - 'update 3': 'By emp1 (Employee) employee says something', + 'update 2': 'By John Smith\nuser says something', + 'update 3': 'By emp1 (Employee)\nemployee says something', } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), expected) @@ -209,6 +211,95 @@ def test_ticket_upload_no_name(self): "data": b"ticket attached data"},), identifier=1) + def test_ticket_upload(self): + result = self.run_command(['ticket', 'upload', '1', + '--path=tests/resources/attachment_upload', + '--name=a_file_name']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', + 'addAttachedFile', + args=({"filename": "a_file_name", + "data": b"ticket attached data"},), + identifier=1) + + def test_init_ticket_results(self): + ticket_mgr = TicketManager(self.client) + ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) + self.assertIsInstance(ticket_table, formatting.KeyValueTable) + + ticket_object = ticket_table.to_python() + self.assertEqual('No Priority', ticket_object['priority']) + self.assertEqual(100, ticket_object['id']) + + def test_init_ticket_results_asigned_user(self): + mock = self.set_mock('SoftLayer_Ticket', 'getObject') + mock.return_value = { + "serviceProviderResourceId": "CS12345", + "id": 100, + "title": "Simple Title", + "priority": 1, + "assignedUser": { + "firstName": "Test", + "lastName": "User" + }, + "status": { + "name": "Closed" + }, + "createDate": "2013-08-01T14:14:04-07:00", + "lastEditDate": "2013-08-01T14:16:47-07:00", + "updates": [{'entry': 'a bot says something'}] + } + + ticket_mgr = TicketManager(self.client) + ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) + self.assertIsInstance(ticket_table, formatting.KeyValueTable) + + ticket_object = ticket_table.to_python() + self.assertEqual('Severity 1 - Critical Impact / Service Down', ticket_object['priority']) + self.assertEqual('Test User', ticket_object['user']) + + def test_ticket_summary(self): + mock = self.set_mock('SoftLayer_Account', 'getObject') + mock.return_value = { + 'openTicketCount': 1, + 'closedTicketCount': 2, + 'openBillingTicketCount': 3, + 'openOtherTicketCount': 4, + 'openSalesTicketCount': 5, + 'openSupportTicketCount': 6, + 'openAccountingTicketCount': 7 + } + expected = [ + {'Status': 'Open', + 'count': [ + {'Type': 'Accounting', 'count': 7}, + {'Type': 'Billing', 'count': 3}, + {'Type': 'Sales', 'count': 5}, + {'Type': 'Support', 'count': 6}, + {'Type': 'Other', 'count': 4}, + {'Type': 'Total', 'count': 1}]}, + {'Status': 'Closed', 'count': 2} + ] + result = self.run_command(['ticket', 'summary']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getObject') + self.assertEqual(expected, json.loads(result.output)) + + def test_ticket_update(self): + result = self.run_command(['ticket', 'update', '100', '--body=Testing']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing'},), identifier=100) + + @mock.patch('click.edit') + def test_ticket_update_no_body(self, edit_mock): + edit_mock.return_value = 'Testing1' + result = self.run_command(['ticket', 'update', '100']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + def test_ticket_json(self): result = self.run_command(['--format=json', 'ticket', 'detail', '1']) expected = {'Case_Number': 'CS123456', From 59b921f41a10ed0ec78e391094e07d06efede89d Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 24 Mar 2020 10:11:08 -0400 Subject: [PATCH 0289/1582] fix the coverage test --- tests/CLI/modules/ticket_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index dcfa44968..a9e305c19 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -42,8 +42,8 @@ def test_detail(self): 'status': 'Closed', 'title': 'Cloud Instance Cancellation - 08/01/13', 'update 1': 'a bot says something', - 'update 2': 'By John Smith\nuser says something', - 'update 3': 'By emp1 (Employee)\nemployee says something', + 'update 2': 'By John Smith user says something', + 'update 3': 'By emp1 (Employee) employee says something', } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), expected) @@ -225,7 +225,7 @@ def test_ticket_upload(self): def test_init_ticket_results(self): ticket_mgr = TicketManager(self.client) - ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + ticket_table = ticket.get_ticket_results(ticket_mgr, False,100) self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) self.assertIsInstance(ticket_table, formatting.KeyValueTable) @@ -253,7 +253,7 @@ def test_init_ticket_results_asigned_user(self): } ticket_mgr = TicketManager(self.client) - ticket_table = ticket.get_ticket_results(ticket_mgr, 100) + ticket_table = ticket.get_ticket_results(ticket_mgr, False, 100) self.assert_called_with('SoftLayer_Ticket', 'getObject', identifier=100) self.assertIsInstance(ticket_table, formatting.KeyValueTable) From fbda3640cdd3524816c776e9a068539ac3c1b099 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 11:33:23 -0400 Subject: [PATCH 0290/1582] Feature vs storage details. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/storage.py | 53 ++++++++ SoftLayer/fixtures/SoftLayer_Account.py | 16 +++ .../SoftLayer_Network_Storage_Iscsi.py | 23 ++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 68 ++++++++++ SoftLayer/managers/vs.py | 28 ++++ tests/CLI/modules/vs/vs_tests.py | 6 + tests/managers/vs/vs_tests.py | 127 ++++++++++++++++++ 8 files changed, 322 insertions(+) create mode 100644 SoftLayer/CLI/virt/storage.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..abeae28fe 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -36,6 +36,7 @@ ('virtual:ready', 'SoftLayer.CLI.virt.ready:cli'), ('virtual:reboot', 'SoftLayer.CLI.virt.power:reboot'), ('virtual:reload', 'SoftLayer.CLI.virt.reload:cli'), + ('virtual:storage', 'SoftLayer.CLI.virt.storage:cli'), ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py new file mode 100644 index 000000000..c88991a2c --- /dev/null +++ b/SoftLayer/CLI/virt/storage.py @@ -0,0 +1,53 @@ +"""Get storage details for a virtual server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get storage details for a virtual server. + """ + vsi = SoftLayer.VSManager(env.client) + vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') + iscsi_storage_data = vsi.get_storage_details(vsi_id, "ISCSI") + nas_storage_data = vsi.get_storage_details(vsi_id, "NAS") + storage_credentials = vsi.get_storage_credentials(vsi_id) + portable_storage = vsi.get_portable_storage(vsi_id) + + table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") + if storage_credentials: + table_credentials.add_row([storage_credentials['credential']['username'], + storage_credentials['credential']['password'], + storage_credentials['name']]) + + table_iscsi = formatting.Table(['LUN name', 'capacity', 'Target address', 'Location', 'Notes']) + for iscsi in iscsi_storage_data: + table_iscsi.add_row([iscsi['username'], iscsi['capacityGb'], + iscsi['serviceResourceBackendIpAddress'], + iscsi['allowedVirtualGuests'][0]['datacenter']['longName'], + iscsi.get('notes', None)]) + + table_portable = formatting.Table(['Description', 'Capacity'], title="Portable Storage") + for portable in portable_storage: + table_portable.add_row([portable.get('description', None), portable.get('capacity', None)]) + + table_nas = formatting.Table(['Volume name', 'capacity', 'Host Name', 'Location', 'Notes'], + title="File Storage Details") + for nas in nas_storage_data: + table_nas.add_row([nas['username'], nas['capacityGb'], + nas['serviceResourceBackendIpAddress'], + nas['allowedVirtualGuests'][0]['datacenter']['longName'], + nas.get('notes', None)]) + + env.fout(table_credentials) + env.fout(table_iscsi) + env.fout(table_portable) + env.fout(table_nas) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ddb2a4354..378890edb 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -852,3 +852,19 @@ } } ] + +getPortableStorageVolumes = [ + { + "capacity": 200, + "createDate": "2018-10-06T04:27:59-06:00", + "description": "Disk 2", + "id": 11111, + "modifyDate": "", + "name": "Disk 2", + "parentId": "", + "storageRepositoryId": 22222, + "typeId": 241, + "units": "GB", + "uuid": "fd477feb-bf32-408e-882f-02540gghgh111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py new file mode 100644 index 000000000..f6683df8c --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py @@ -0,0 +1,23 @@ +getObject = { + "id": 11111, + "allowedVirtualGuests": [ + { + "id": 22222, + "allowedHost": { + "accountId": 12345, + "id": 18311111, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6222222, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "12345", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 1522222, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrmKewos", + "username": "SL02SU322222-V62922222" + } + } + } + ] +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 270ecf2ad..0eb728b03 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -670,3 +670,71 @@ } } ] + +getAttachedNetworkStorages = [ + { + "accountId": 11111, + "capacityGb": 20, + "createDate": "2018-04-05T05:15:49-06:00", + "id": 22222, + "nasType": "NAS", + "serviceProviderId": 1, + "storageTypeId": "13", + "username": "SL02SEV311111_11", + "allowedVirtualGuests": [ + { + "id": 12345, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "fsn-fra0201a-fz.service.softlayer.com", + "serviceResourceName": "Storage Type 02 File Aggregate stfm-fra0201a" + }, + { + "accountId": 11111, + "capacityGb": 12000, + "createDate": "2018-01-28T04:57:30-06:00", + "id": 3777111, + "nasType": "ISCSI", + "notes": "BlockStorage12T", + "password": "", + "serviceProviderId": 1, + "storageTypeId": "7", + "username": "SL02SEL32222-9", + "allowedVirtualGuests": [ + { + "id": 629222, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "10.31.95.152", + "serviceResourceName": "Storage Type 02 Block Aggregate stbm-fra0201a" + } +] + +getAllowedHost = { + "accountId": 11111, + "credentialId": 22222, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6291111, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 44444, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } +} diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b1391bc3a..30298ef9d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -51,6 +51,7 @@ def __init__(self, client, ordering_manager=None): self.account = client['Account'] self.guest = client['Virtual_Guest'] self.package_svc = client['Product_Package'] + self.storage_iscsi = client['SoftLayer_Network_Storage_Iscsi'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) @@ -1123,3 +1124,30 @@ def _get_price_id_for_upgrade(self, package_items, option, value, public=True): return price['id'] else: return price['id'] + + def get_storage_details(self, instance_id, nas_type): + """Returns the virtual server attached network storage. + + :param int instance_id: Id of the virtual server + :param nas_type: storage type. + """ + nas_type = nas_type + mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ + 'allowedVirtualGuests[id,datacenter]]' + return self.guest.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) + + def get_storage_credentials(self, instance_id): + """Returns the virtual server storage credentials. + + :param int instance_id: Id of the virtual server + """ + mask = 'mask[credential]' + return self.guest.getAllowedHost(mask=mask, id=instance_id) + + def get_portable_storage(self, instance_id): + """Returns the virtual server storage credentials. + + :param int instance_id: Id of the virtual server + """ + object_filter = {"portableStorageVolumes": {"blockDevices": {"guest": {"id": {"operation": instance_id}}}}} + return self.account.getPortableStorageVolumes(filter=object_filter) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 2c79f42bf..f22a15d2e 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -740,3 +740,9 @@ def test_bandwidth_vs_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + def test_vs_storage(self): + result = self.run_command( + ['vs', 'storage', '100']) + + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 47fcaf20d..f55430697 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -925,3 +925,130 @@ def test_get_bandwidth_allocation_with_allotment(self): result = self.vs.get_bandwidth_allocation(1234) self.assertEqual(2000, int(result['allotment']['amount'])) + + def test_get_storage_iscsi_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + } + ] + + result = self.vs.get_storage_details(1234, 'ISCSI') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + }], result) + + def test_get_storage_iscsi_empty_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.vs.get_storage_details(1234, 'ISCSI') + + self.assertEqual([], result) + + def test_get_storage_nas_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + } + ] + + result = self.vs.get_storage_details(1234, 'NAS') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + }], result) + + def test_get_storage_nas_empty_details(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.vs.get_storage_details(1234, 'NAS') + + self.assertEqual([], result) + + def test_get_storage_credentials(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAllowedHost') + mock.return_value = { + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + } + + result = self.vs.get_storage_credentials(1234) + + self.assertEqual({ + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + }, result) + + def test_get_none_storage_credentials(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getAllowedHost') + mock.return_value = None + + result = self.vs.get_storage_credentials(1234) + + self.assertEqual(None, result) + + def test_get_portable_storage(self): + result = self.vs.get_portable_storage(1234) + self.assert_called_with('SoftLayer_Account', + 'getPortableStorageVolumes') + + self.assertEqual([ + { + "capacity": 200, + "createDate": "2018-10-06T04:27:59-06:00", + "description": "Disk 2", + "id": 11111, + "modifyDate": "", + "name": "Disk 2", + "parentId": "", + "storageRepositoryId": 22222, + "typeId": 241, + "units": "GB", + "uuid": "fd477feb-bf32-408e-882f-02540gghgh111" + } + ], result) + + def test_get_portable_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getPortableStorageVolumes') + mock.return_value = [] + + result = self.vs.get_portable_storage(1234) + + self.assertEqual([], result) From 4543e6cbdcd6c41d99721bbdcc93bb4afab029e7 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 14:39:52 -0400 Subject: [PATCH 0291/1582] Feature hardware storage details. --- SoftLayer/CLI/hardware/storage.py | 47 +++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 68 +++++++++++++ SoftLayer/managers/hardware.py | 21 +++- tests/CLI/modules/server_tests.py | 6 ++ tests/managers/hardware_tests.py | 99 ++++++++++++++++++- 6 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/hardware/storage.py diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py new file mode 100644 index 000000000..1646aafb6 --- /dev/null +++ b/SoftLayer/CLI/hardware/storage.py @@ -0,0 +1,47 @@ +"""Get storage details for a hardware server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get storage details for a hardware server. + """ + hardware = SoftLayer.HardwareManager(env.client) + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") + nas_storage_data = hardware.get_storage_details(hardware_id, "NAS") + storage_credentials = hardware.get_storage_credentials(hardware_id) + + table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") + if storage_credentials: + table_credentials.add_row([storage_credentials['credential']['username'], + storage_credentials['credential']['password'], + storage_credentials['name']]) + + table_iscsi = formatting.Table(['LUN name', 'capacity', 'Target address', 'Location', 'Notes']) + for iscsi in iscsi_storage_data: + table_iscsi.add_row([iscsi['username'], iscsi['capacityGb'], + iscsi['serviceResourceBackendIpAddress'], + iscsi['allowedHardware'][0]['datacenter']['longName'], + iscsi.get('notes', None)]) + + table_nas = formatting.Table(['Volume name', 'capacity', 'Host Name', 'Location', 'Notes'], + title="File Storage Details") + for nas in nas_storage_data: + table_nas.add_row([nas['username'], nas['capacityGb'], + nas['serviceResourceBackendIpAddress'], + nas['allowedHardware'][0]['datacenter']['longName'], + nas.get('notes', None)]) + + env.fout(table_credentials) + env.fout(table_iscsi) + env.fout(table_nas) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index abeae28fe..e18190d56 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -246,6 +246,7 @@ ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), + ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 47e9a1bcb..920b91bf1 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -149,3 +149,71 @@ ] getMetricTrackingObjectId = 1000 + +getAttachedNetworkStorages = [ + { + "accountId": 11111, + "capacityGb": 20, + "createDate": "2018-04-05T05:15:49-06:00", + "id": 22222, + "nasType": "NAS", + "serviceProviderId": 1, + "storageTypeId": "13", + "username": "SL02SEV311111_11", + "allowedHardware": [ + { + "id": 12345, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "fsn-fra0201a-fz.service.softlayer.com", + "serviceResourceName": "Storage Type 02 File Aggregate stfm-fra0201a" + }, + { + "accountId": 11111, + "capacityGb": 12000, + "createDate": "2018-01-28T04:57:30-06:00", + "id": 3777111, + "nasType": "ISCSI", + "notes": "BlockStorage12T", + "password": "", + "serviceProviderId": 1, + "storageTypeId": "7", + "username": "SL02SEL32222-9", + "allowedHardware": [ + { + "id": 629222, + "datacenter": { + "id": 449506, + "longName": "Frankfurt 2", + "name": "fra02", + "statusId": 2 + } + } + ], + "serviceResourceBackendIpAddress": "10.31.95.152", + "serviceResourceName": "Storage Type 02 Block Aggregate stbm-fra0201a" + } +] + +getAllowedHost = { + "accountId": 11111, + "credentialId": 22222, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableId": 6291111, + "resourceTableName": "VIRTUAL_GUEST", + "credential": { + "accountId": "11111", + "createDate": "2020-03-20T13:35:47-06:00", + "id": 44444, + "nasCredentialTypeId": 2, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fa4ee42a8..472208880 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -10,9 +10,9 @@ import time import SoftLayer +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.managers import ordering -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -702,6 +702,25 @@ def get_bandwidth_allocation(self, instance_id): return {'allotment': allotment.get('allocation'), 'usage': usage} return {'allotment': allotment, 'usage': usage} + def get_storage_details(self, instance_id, nas_type): + """Returns the hardware server attached network storage. + + :param int instance_id: Id of the hardware server + :param nas_type: storage type. + """ + nas_type = nas_type + mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ + 'allowedHardware[id,datacenter]]' + return self.hardware.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) + + def get_storage_credentials(self, instance_id): + """Returns the hardware server storage credentials. + + :param int instance_id: Id of the hardware server + """ + mask = 'mask[credential]' + return self.hardware.getAllowedHost(mask=mask, id=instance_id) + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 14f8e9201..758bd6371 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -826,3 +826,9 @@ def test_dns_sync_misc_exception(self, confirm_mock): result = self.run_command(['hw', 'dns-sync', '-a', '1000']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_hardware_storage(self): + result = self.run_command( + ['hw', 'storage', '100']) + + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 5710dd0ae..b3afe269f 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -9,7 +9,6 @@ import mock import SoftLayer - from SoftLayer import fixtures from SoftLayer import managers from SoftLayer import testing @@ -467,6 +466,104 @@ def test_get_bandwidth_allocation_no_allotment(self): self.assertEqual(None, result['allotment']) + def test_get_storage_iscsi_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + } + ] + + result = self.hardware.get_storage_details(1234, 'ISCSI') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777123, + "nasType": "ISCSI", + "username": "SL02SEL31111-9", + }], result) + + def test_get_storage_iscsi_empty_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.hardware.get_storage_details(1234, 'ISCSI') + + self.assertEqual([], result) + + def test_get_storage_nas_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [ + { + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + } + ] + + result = self.hardware.get_storage_details(1234, 'NAS') + + self.assertEqual([{ + "accountId": 11111, + "capacityGb": 12000, + "id": 3777111, + "nasType": "NAS", + "username": "SL02SEL32222-9", + }], result) + + def test_get_storage_nas_empty_details(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAttachedNetworkStorages') + mock.return_value = [] + + result = self.hardware.get_storage_details(1234, 'NAS') + + self.assertEqual([], result) + + def test_get_storage_credentials(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAllowedHost') + mock.return_value = { + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "HARDWARE", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + } + + result = self.hardware.get_storage_credentials(1234) + + self.assertEqual({ + "accountId": 11111, + "id": 33333, + "name": "iqn.2020-03.com.ibm:sl02su11111-v62941551", + "resourceTableName": "HARDWARE", + "credential": { + "accountId": "11111", + "id": 44444, + "password": "SjFDCpHrjskfj", + "username": "SL02SU11111-V62941551" + } + }, result) + + def test_get_none_storage_credentials(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getAllowedHost') + mock.return_value = None + + result = self.hardware.get_storage_credentials(1234) + + self.assertEqual(None, result) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): From 84cf1b6e5039cbeedb355e46151391428ec8643c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:08:55 -0500 Subject: [PATCH 0292/1582] version to 5.8.6 --- 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 444c459cb..971b16ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## [5.8.6] - 2012-03-26 +https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 + +- #1222 Get load balancer (LBaaS) by name +- #1221 Added version checker +- #1227 Updated unit test suite for TravisCI to run properly +- #1225 Add note about using multiple colon symbols not working when setting tags. +- #1228 Support ordering [Dependent Duplicate Volumes](https://cloud.ibm.com/docs/BlockStorage?topic=BlockStorage-dependentduplicate) +- #1233 Refactored File/Block managers to reduce duplicated code. +- #1231 Added Refresh functions for Dependent Duplicate Volumes +- #801 Added support for JSON styled parameters and object filters +- #1234 Added ability to change which datacenters an image template was stored in + ## [5.8.5] - 2012-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 8bb1585fd..041f3b013 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.5' +VERSION = 'v5.8.6' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index b88b324c0..a7c4eb61e 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.5', + version='5.8.6', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From a6818155c1a46956f44261ca7948eb64745193c7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:18:23 -0500 Subject: [PATCH 0293/1582] updated release dates to proper year --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 971b16ba1..e3c10eda1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [5.8.6] - 2012-03-26 +## [5.8.6] - 2020-03-26 https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #1222 Get load balancer (LBaaS) by name @@ -14,7 +14,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #1234 Added ability to change which datacenters an image template was stored in -## [5.8.5] - 2012-01-29 +## [5.8.5] - 2020-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 - #1195 Fixed an issue with `slcli vs dns-sync --ptr`. Added `slcli hw dns-sync` From cd2bd8e1ea08c18e1a9f288fb3147a9b0cc27bed Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 17:42:17 -0400 Subject: [PATCH 0294/1582] Add local disks information for vs and hard drives information for hardware server. --- SoftLayer/CLI/hardware/storage.py | 11 +++ SoftLayer/CLI/virt/storage.py | 20 +++++ .../fixtures/SoftLayer_Hardware_Server.py | 25 ++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 29 +++++++ SoftLayer/managers/hardware.py | 7 ++ SoftLayer/managers/vs.py | 10 ++- tests/managers/hardware_tests.py | 62 ++++++++++++++ tests/managers/vs/vs_tests.py | 80 +++++++++++++++++++ 8 files changed, 243 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 1646aafb6..69232922c 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -20,6 +20,7 @@ def cli(env, identifier): iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") nas_storage_data = hardware.get_storage_details(hardware_id, "NAS") storage_credentials = hardware.get_storage_credentials(hardware_id) + hard_drives = hardware.get_hard_drives(hardware_id) table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") if storage_credentials: @@ -42,6 +43,16 @@ def cli(env, identifier): nas['allowedHardware'][0]['datacenter']['longName'], nas.get('notes', None)]) + table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") + for drives in hard_drives: + table_hard_drives.add_row([drives['hardwareComponentModel']['hardwareGenericComponentModel'] + ['hardwareComponentType']['type'], drives['hardwareComponentModel'] + ['manufacturer'] + " " + drives['hardwareComponentModel']['name'], + str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + + " " + str(drives['hardwareComponentModel']['hardwareGenericComponentModel'] + ['units']), drives['serialNumber']]) + env.fout(table_credentials) env.fout(table_iscsi) env.fout(table_nas) + env.fout(table_hard_drives) diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index c88991a2c..1fdd1a78d 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -21,6 +21,7 @@ def cli(env, identifier): nas_storage_data = vsi.get_storage_details(vsi_id, "NAS") storage_credentials = vsi.get_storage_credentials(vsi_id) portable_storage = vsi.get_portable_storage(vsi_id) + local_disks = vsi.get_local_disks(vsi_id) table_credentials = formatting.Table(['Username', 'Password', 'IQN'], title="Block Storage Details \n iSCSI") if storage_credentials: @@ -47,7 +48,26 @@ def cli(env, identifier): nas['allowedVirtualGuests'][0]['datacenter']['longName'], nas.get('notes', None)]) + table_local_disks = formatting.Table(['Type', 'Name', 'Capacity'], title="Other storage details") + for disks in local_disks: + if 'diskImage' in disks: + table_local_disks.add_row([get_local_type(disks), disks['mountType'], + str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + env.fout(table_credentials) env.fout(table_iscsi) env.fout(table_portable) env.fout(table_nas) + env.fout(table_local_disks) + + +def get_local_type(disks): + """Returns the virtual server local disk type. + + :param disks: virtual serve local disks. + """ + disk_type = 'System' + if 'SWAP' in disks['diskImage']['description']: + disk_type = 'Swap' + + return disk_type diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 920b91bf1..e90288753 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -217,3 +217,28 @@ "username": "SL02SU11111-V62941551" } } + +getHardDrives = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 0eb728b03..c42963c8e 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -738,3 +738,32 @@ "username": "SL02SU11111-V62941551" } } + +getBlockDevices = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + }, + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 472208880..ca19a51f3 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -721,6 +721,13 @@ def get_storage_credentials(self, instance_id): mask = 'mask[credential]' return self.hardware.getAllowedHost(mask=mask, id=instance_id) + def get_hard_drives(self, instance_id): + """Returns the hardware server hard drives. + + :param int instance_id: Id of the hardware server + """ + return self.hardware.getHardDrives(id=instance_id) + def _get_extra_price_id(items, key_name, hourly, location): """Returns a price id attached to item with the given key_name.""" diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 30298ef9d..ade6aa68d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1145,9 +1145,17 @@ def get_storage_credentials(self, instance_id): return self.guest.getAllowedHost(mask=mask, id=instance_id) def get_portable_storage(self, instance_id): - """Returns the virtual server storage credentials. + """Returns the virtual server portable storage. :param int instance_id: Id of the virtual server """ object_filter = {"portableStorageVolumes": {"blockDevices": {"guest": {"id": {"operation": instance_id}}}}} return self.account.getPortableStorageVolumes(filter=object_filter) + + def get_local_disks(self, instance_id): + """Returns the virtual server local disks. + + :param int instance_id: Id of the virtual server + """ + mask = 'mask[diskImage]' + return self.guest.getBlockDevices(mask=mask, id=instance_id) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index b3afe269f..9ac63c224 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -564,6 +564,68 @@ def test_get_none_storage_credentials(self): self.assertEqual(None, result) + def test_get_hard_drives(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ] + + result = self.hardware.get_hard_drives(1234) + + self.assertEqual([ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "serviceProviderId": 1, + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ], result) + + def test_get_hard_drive_empty(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [] + + result = self.hardware.get_hard_drives(1234) + + self.assertEqual([], result) + class HardwareHelperTests(testing.TestCase): def test_get_extra_price_id_no_items(self): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index f55430697..40fb3063f 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1052,3 +1052,83 @@ def test_get_portable_storage_empty(self): result = self.vs.get_portable_storage(1234) self.assertEqual([], result) + + def test_get_local_disks_system(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ], result) + + def test_get_local_disks_empty(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([], result) + + def test_get_local_disks_swap(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ] + + result = self.vs.get_local_disks(1234) + + self.assertEqual([ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ], result) From 047620a223eca84b4a2e52329d860c6a3c593454 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:52:52 -0500 Subject: [PATCH 0295/1582] v5.8.7 release --- CHANGELOG.md | 7 ++++--- SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c10eda1..2d271cd86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [5.8.6] - 2020-03-26 -https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 +## [5.8.7] - 2020-03-26 +https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.7 - #1222 Get load balancer (LBaaS) by name - #1221 Added version checker @@ -13,6 +13,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.6 - #801 Added support for JSON styled parameters and object filters - #1234 Added ability to change which datacenters an image template was stored in +## [5.8.6] - Skipped ## [5.8.5] - 2020-01-29 https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 @@ -33,7 +34,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 ## [5.8.4] - 2019-12-20 -https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 +https://github.com/softlayer/softlayer-python/compare/v5.8.3...5vhttps://pypi.org/help/#file-name-reuse.8.4 - #1199 Fix block storage failback and failover. - #1202 Order a virtual server private. diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 041f3b013..e651d91ca 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.6' +VERSION = 'v5.8.7' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index a7c4eb61e..d8e9f566f 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.6', + version='5.8.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 1ac8bcb9df823ae97ad38755b033673283034dca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 26 Mar 2020 16:53:50 -0500 Subject: [PATCH 0296/1582] v5.8.7 release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d271cd86..965e9967f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.4...v5.8.5 ## [5.8.4] - 2019-12-20 -https://github.com/softlayer/softlayer-python/compare/v5.8.3...5vhttps://pypi.org/help/#file-name-reuse.8.4 +https://github.com/softlayer/softlayer-python/compare/v5.8.3...v5.8.4 - #1199 Fix block storage failback and failover. - #1202 Order a virtual server private. From 644cbc0601782490613a4d168011a48de8058ec1 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 18:07:16 -0400 Subject: [PATCH 0297/1582] Fix tox analysis. --- SoftLayer/CLI/hardware/storage.py | 4 ++-- SoftLayer/CLI/virt/storage.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py | 2 +- SoftLayer/managers/hardware.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 69232922c..2a4501298 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -13,8 +13,8 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get storage details for a hardware server. - """ + """Get storage details for a hardware server.""" + hardware = SoftLayer.HardwareManager(env.client) hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') iscsi_storage_data = hardware.get_storage_details(hardware_id, "ISCSI") diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 1fdd1a78d..8d1b65854 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -13,8 +13,8 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Get storage details for a virtual server. - """ + """Get storage details for a virtual server.""" + vsi = SoftLayer.VSManager(env.client) vsi_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') iscsi_storage_data = vsi.get_storage_details(vsi_id, "ISCSI") diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py index f6683df8c..04c228476 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Iscsi.py @@ -20,4 +20,4 @@ } } ] -} \ No newline at end of file +} diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ca19a51f3..2ac20b2b2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -10,9 +10,9 @@ import time import SoftLayer -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.managers import ordering +from SoftLayer import utils LOGGER = logging.getLogger(__name__) From a7fe50241121ba4cf8b1ae68113979157505fc09 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 26 Mar 2020 18:22:26 -0400 Subject: [PATCH 0298/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 - SoftLayer/managers/vs.py | 1 - 2 files changed, 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 2ac20b2b2..99ab45b9c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -708,7 +708,6 @@ def get_storage_details(self, instance_id, nas_type): :param int instance_id: Id of the hardware server :param nas_type: storage type. """ - nas_type = nas_type mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ 'allowedHardware[id,datacenter]]' return self.hardware.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index ade6aa68d..e63d7a80f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1131,7 +1131,6 @@ def get_storage_details(self, instance_id, nas_type): :param int instance_id: Id of the virtual server :param nas_type: storage type. """ - nas_type = nas_type mask = 'mask[id,username,capacityGb,notes,serviceResourceBackendIpAddress,' \ 'allowedVirtualGuests[id,datacenter]]' return self.guest.getAttachedNetworkStorages(nas_type, mask=mask, id=instance_id) From cbe7c98a3927e9b598ad51dcf64680aaad40718d Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 31 Mar 2020 20:09:33 -0400 Subject: [PATCH 0299/1582] implement the new feature hw billing and vs billing --- SoftLayer/CLI/hardware/billing.py | 38 +++++++++++++++++++++ SoftLayer/CLI/virt/billing.py | 38 +++++++++++++++++++++ tests/CLI/modules/server_tests.py | 55 +++++++++++++++++++++---------- tests/CLI/modules/vs/vs_tests.py | 52 +++++++++++++++++++---------- 4 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 SoftLayer/CLI/hardware/billing.py create mode 100644 SoftLayer/CLI/virt/billing.py diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py new file mode 100644 index 000000000..2131f999c --- /dev/null +++ b/SoftLayer/CLI/hardware/billing.py @@ -0,0 +1,38 @@ +"""Get billing 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 +from SoftLayer.CLI import helpers +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get billing for a hardware device.""" + hardware = SoftLayer.HardwareManager(env.client) + + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + result = hardware.get_hardware(hardware_id) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['hardwareId', identifier]) + + table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) + table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + + price_table = formatting.Table(['Item', 'Recurring Price']) + for item in utils.lookup(result, 'billingItem', 'children') or []: + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + + table.add_row(['prices', price_table]) + env.fout(table) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py new file mode 100644 index 000000000..500692d26 --- /dev/null +++ b/SoftLayer/CLI/virt/billing.py @@ -0,0 +1,38 @@ +"""Get billing for a virtual device.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Get billing for a virtual device.""" + virtual = SoftLayer.VSManager(env.client) + + virtual_id = helpers.resolve_id(virtual.resolve_ids, identifier, 'virtual') + result = virtual.get_instance(virtual_id) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['VirtuallId', identifier]) + + table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) + table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + + price_table = formatting.Table(['Recurring Price']) + for item in utils.lookup(result, 'billingItem', 'children') or []: + price_table.add_row([item['nextInvoiceTotalRecurringAmount']]) + + table.add_row(['prices', price_table]) + env.fout(table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 14f8e9201..16af3a235 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -659,19 +659,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']) @@ -714,12 +714,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) @@ -826,3 +826,22 @@ def test_dns_sync_misc_exception(self, confirm_mock): result = self.run_command(['hw', 'dns-sync', '-a', '1000']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_billing(self): + result = self.run_command(['hw', 'billing', '123456']) + billing_json = { + "hardwareId": "123456", + "BillingIttem": 6327, + "recurringFee": 1.54, + "Total": 16.08, + "provisionDate": None, + "prices": [ + { + "Item": "test", + "Recurring Price": 1 + } + ] + } + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), billing_json) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 2c79f42bf..e82bf4e87 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -320,19 +320,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']) @@ -375,12 +375,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) @@ -740,3 +740,19 @@ def test_bandwidth_vs_quite(self): self.assertEqual(output_summary[1]['Max Date'], date) self.assertEqual(output_summary[2]['Max GB'], 0.1172) self.assertEqual(output_summary[3]['Sum GB'], 0.0009) + + def test_billing(self): + result = self.run_command(['vs', 'billing', '123456']) + vir_billing = { + "BillingIttem": 6327, + "Total": 1.54, + "VirtuallId": "123456", + "prices": [{"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}, + {"Recurring Price": 1}], + "provisionDate": None, + "recurringFee": None} + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), vir_billing) From 0d69281a756f257935ad187f977e28d3ec9ec911 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 1 Apr 2020 08:34:03 -0400 Subject: [PATCH 0300/1582] add the routes file --- SoftLayer/CLI/routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..b68fd8e4a 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -20,6 +20,7 @@ ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), + ('virtual:billing', 'SoftLayer.CLI.virt.billing:cli'), ('virtual:cancel', 'SoftLayer.CLI.virt.cancel:cli'), ('virtual:capture', 'SoftLayer.CLI.virt.capture:cli'), ('virtual:create', 'SoftLayer.CLI.virt.create:cli'), @@ -163,7 +164,6 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), - ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), @@ -231,6 +231,7 @@ ('hardware:create', 'SoftLayer.CLI.hardware.create:cli'), ('hardware:create-options', 'SoftLayer.CLI.hardware.create_options:cli'), ('hardware:detail', 'SoftLayer.CLI.hardware.detail:cli'), + ('hardware:billing', 'SoftLayer.CLI.hardware.billing:cli'), ('hardware:edit', 'SoftLayer.CLI.hardware.edit:cli'), ('hardware:list', 'SoftLayer.CLI.hardware.list:cli'), ('hardware:power-cycle', 'SoftLayer.CLI.hardware.power:power_cycle'), From 845760df58f39ca10232bd85902b83db8d1ed158 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 2 Apr 2020 15:40:29 -0400 Subject: [PATCH 0301/1582] fix Christopher code review --- SoftLayer/CLI/hardware/billing.py | 4 ++-- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/billing.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 2131f999c..f1b1a8501 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -23,9 +23,9 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['hardwareId', identifier]) + table.add_row(['Id', identifier]) - table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b68fd8e4a..d7e6334c8 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -164,6 +164,7 @@ ('image:list', 'SoftLayer.CLI.image.list:cli'), ('image:import', 'SoftLayer.CLI.image.import:cli'), ('image:export', 'SoftLayer.CLI.image.export:cli'), + ('image:datacenter', 'SoftLayer.CLI.image.datacenter:cli'), ('ipsec', 'SoftLayer.CLI.vpn.ipsec'), ('ipsec:configure', 'SoftLayer.CLI.vpn.ipsec.configure:cli'), diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 500692d26..872f708a0 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -23,9 +23,9 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['VirtuallId', identifier]) + table.add_row(['Id', identifier]) - table.add_row(['BillingIttem', utils.lookup(result, 'billingItem', 'id')]) + table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) From ff42108a6a8841353c0839da51ea5c9bab1c376c Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Thu, 2 Apr 2020 16:12:12 -0400 Subject: [PATCH 0302/1582] fix Christopher code review --- SoftLayer/CLI/hardware/billing.py | 6 +++--- SoftLayer/CLI/virt/billing.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index f1b1a8501..03b32e7f6 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -26,13 +26,13 @@ def cli(env, identifier): table.add_row(['Id', identifier]) table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) - table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Item', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['Description'], item['NextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/SoftLayer/CLI/virt/billing.py b/SoftLayer/CLI/virt/billing.py index 872f708a0..7312de8d8 100644 --- a/SoftLayer/CLI/virt/billing.py +++ b/SoftLayer/CLI/virt/billing.py @@ -26,9 +26,9 @@ def cli(env, identifier): table.add_row(['Id', identifier]) table.add_row(['Billing Item Id', utils.lookup(result, 'billingItem', 'id')]) - table.add_row(['recurringFee', utils.lookup(result, 'billingItem', 'recurringFee')]) + table.add_row(['Recurring Fee', utils.lookup(result, 'billingItem', 'recurringFee')]) table.add_row(['Total', utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount')]) - table.add_row(['provisionDate', utils.lookup(result, 'billingItem', 'provisionDate')]) + table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: From 1091825383df7419ca3989c55d8ae59b7a1c2b7b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 2 Apr 2020 19:03:42 -0400 Subject: [PATCH 0303/1582] Add local disk information for vs and hardware detail. --- SoftLayer/CLI/hardware/detail.py | 11 +++++++ SoftLayer/CLI/hardware/storage.py | 13 ++++---- SoftLayer/CLI/virt/detail.py | 20 +++++++++++++ tests/CLI/modules/server_tests.py | 33 ++++++++++++++++++++ tests/CLI/modules/vs/vs_tests.py | 50 +++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index 89f0cb0ee..d48a7e5ed 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -27,6 +27,7 @@ def cli(env, identifier, passwords, price): hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') result = hardware.get_hardware(hardware_id) result = utils.NestedDict(result) + hard_drives = hardware.get_hard_drives(hardware_id) operating_system = utils.lookup(result, 'operatingSystem', 'softwareLicense', 'softwareDescription') or {} memory = formatting.gb(result.get('memoryCapacity', 0)) @@ -34,6 +35,15 @@ def cli(env, identifier, passwords, price): if utils.lookup(result, 'billingItem') != []: owner = utils.lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username') + table_hard_drives = formatting.Table(['Name', 'Capacity', 'Serial #']) + for drives in hard_drives: + name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] + capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( + drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) + serial = drives['serialNumber'] + + table_hard_drives.add_row([name, capacity, serial]) + table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier'] or formatting.blank()]) table.add_row(['hostname', result['hostname']]) @@ -43,6 +53,7 @@ def cli(env, identifier, passwords, price): table.add_row(['datacenter', result['datacenter']['name'] or formatting.blank()]) table.add_row(['cores', result['processorPhysicalCoreAmount']]) table.add_row(['memory', memory]) + table.add_row(['drives', table_hard_drives]) table.add_row(['public_ip', result['primaryIpAddress'] or formatting.blank()]) table.add_row(['private_ip', result['primaryBackendIpAddress'] or formatting.blank()]) table.add_row(['ipmi_ip', result['networkManagementIpAddress'] or formatting.blank()]) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 2a4501298..46cb30be8 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -45,12 +45,13 @@ def cli(env, identifier): table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") for drives in hard_drives: - table_hard_drives.add_row([drives['hardwareComponentModel']['hardwareGenericComponentModel'] - ['hardwareComponentType']['type'], drives['hardwareComponentModel'] - ['manufacturer'] + " " + drives['hardwareComponentModel']['name'], - str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) - + " " + str(drives['hardwareComponentModel']['hardwareGenericComponentModel'] - ['units']), drives['serialNumber']]) + type = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] + name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] + capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( + drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) + serial = drives['serialNumber'] + + table_hard_drives.add_row([type, name, capacity, serial]) env.fout(table_credentials) env.fout(table_iscsi) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 131117df2..d17e489f2 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -32,6 +32,13 @@ def cli(env, identifier, passwords=False, price=False): vs_id = helpers.resolve_id(vsi.resolve_ids, identifier, 'VS') result = vsi.get_instance(vs_id) result = utils.NestedDict(result) + local_disks = vsi.get_local_disks(vs_id) + + table_local_disks = formatting.Table(['Type', 'Name', 'Capacity']) + for disks in local_disks: + if 'diskImage' in disks: + table_local_disks.add_row([get_local_type(disks), disks['mountType'], + str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier']]) @@ -57,6 +64,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['os_version', operating_system.get('version', '-')]) table.add_row(['cores', result['maxCpu']]) table.add_row(['memory', formatting.mb_to_gb(result['maxMemory'])]) + table.add_row(['drives', table_local_disks]) table.add_row(['public_ip', result.get('primaryIpAddress', '-')]) table.add_row(['private_ip', result.get('primaryBackendIpAddress', '-')]) table.add_row(['private_only', result['privateNetworkOnlyFlag']]) @@ -192,3 +200,15 @@ def _get_security_table(result): return secgroup_table else: return None + + +def get_local_type(disks): + """Returns the virtual server local disk type. + + :param disks: virtual serve local disks. + """ + disk_type = 'System' + if 'SWAP' in disks['diskImage']['description']: + disk_type = 'Swap' + + return disk_type diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 758bd6371..65feef1e7 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -139,6 +139,39 @@ def test_detail_empty_allotment(self): '-', ) + def test_detail_drives(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getHardDrives') + mock.return_value = [ + { + "id": 11111, + "serialNumber": "z1w4sdf", + "hardwareComponentModel": { + "capacity": "1000", + "description": "SATAIII:2000:8300:Constellation", + "id": 111, + "manufacturer": "Seagate", + "name": "Constellation ES", + "hardwareGenericComponentModel": { + "capacity": "1000", + "units": "GB", + "hardwareComponentType": { + "id": 1, + "keyName": "HARD_DRIVE", + "type": "Hard Drive", + "typeParentId": 5 + } + } + } + } + ] + result = self.run_command(['server', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '1000 GB') + self.assertEqual(output['drives'][0]['Name'], 'Seagate Constellation ES') + self.assertEqual(output['drives'][0]['Serial #'], 'z1w4sdf') + def test_list_servers(self): result = self.run_command(['server', 'list', '--tag=openstack']) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index f22a15d2e..5c1f03f1b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -216,6 +216,56 @@ def test_detail_vs_empty_allotment(self): '-', ) + def test_detail_drives_system(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "createDate": "2018-10-06T04:27:35-06:00", + "device": "0", + "id": 11111, + "mountType": "Disk", + "diskImage": { + "capacity": 100, + "description": "adns.vmware.com", + "id": 72222, + "name": "adns.vmware.com", + "units": "GB", + } + } + ] + result = self.run_command(['vs', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '100 GB') + self.assertEqual(output['drives'][0]['Name'], 'Disk') + self.assertEqual(output['drives'][0]['Type'], 'System') + + def test_detail_drives_swap(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'getBlockDevices') + mock.return_value = [ + { + "device": "1", + "id": 22222, + "mountType": "Disk", + "statusId": 1, + "diskImage": { + "capacity": 2, + "description": "6211111-SWAP", + "id": 33333, + "name": "6211111-SWAP", + "units": "GB", + } + } + ] + result = self.run_command(['vs', 'detail', '100']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output['drives'][0]['Capacity'], '2 GB') + self.assertEqual(output['drives'][0]['Name'], 'Disk') + self.assertEqual(output['drives'][0]['Type'], 'Swap') + def test_detail_vs_dedicated_host_not_found(self): ex = SoftLayerAPIError('SoftLayer_Exception', 'Not found') mock = self.set_mock('SoftLayer_Virtual_DedicatedHost', 'getObject') From 187c0ca8541c59473e7d2129a0c93a7dafb7c1d2 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 27 Mar 2020 10:06:12 -0400 Subject: [PATCH 0304/1582] add user vpn manual config flag --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/user/vpn_manual.py | 28 ++++++++++++++++++++++++++++ SoftLayer/managers/user.py | 9 +++++++++ tests/CLI/modules/user_tests.py | 18 +++++++++++++++++- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/user/vpn_manual.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..6d976c3f4 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -304,6 +304,7 @@ ('user:edit-details', 'SoftLayer.CLI.user.edit_details:cli'), ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), + ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), diff --git a/SoftLayer/CLI/user/vpn_manual.py b/SoftLayer/CLI/user/vpn_manual.py new file mode 100644 index 000000000..fb8ac78fc --- /dev/null +++ b/SoftLayer/CLI/user/vpn_manual.py @@ -0,0 +1,28 @@ +"""List Users.""" +# :license: MIT, see LICENSE for more details. + + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('user') +@click.option('--enable/--disable', default=True, + help="Whether enable or disable vpnManualConfig flag.") +@environment.pass_env +def cli(env, user, enable): + """Enable or disable user vpn subnets manual config""" + mgr = SoftLayer.UserManager(env.client) + user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') + + result = mgr.vpn_manual(user_id, enable) + message = "{} vpn manual config {}".format(user, 'enable' if enable else 'disable') + + if result: + click.secho(message, fg='green') + else: + click.secho("Failed to update {}".format(user), fg='red') diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 247071381..df2d7b454 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -267,6 +267,15 @@ def add_api_authentication_key(self, user_id): """ return self.user_service.addApiAuthenticationKey(id=user_id) + def vpn_manual(self, user_id, value): + """Set vpnManualConfig flag + + :param int user_id: User to edit + :param bool value: Value for vpnManualConfig flag + """ + user_object = {'vpnManualConfig': value} + return self.edit_user(user_id, user_object) + def _keyname_search(haystack, needle): for item in haystack: diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 79554fc85..d69e36a27 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -14,7 +14,6 @@ class UserCLITests(testing.TestCase): - """User list tests""" def test_user_list(self): @@ -153,6 +152,7 @@ def test_edit_perms_from_user(self): self.assert_called_with('SoftLayer_User_Customer', 'addBulkPortalPermission', identifier=11100) """User create tests""" + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user(self, confirm_mock): confirm_mock.return_value = True @@ -228,6 +228,7 @@ def test_create_user_from_user(self, confirm_mock): self.assert_called_with('SoftLayer_User_Customer', 'getObject', identifier=1234) """User edit-details tests""" + @mock.patch('SoftLayer.CLI.user.edit_details.click') def test_edit_details(self, click): result = self.run_command(['user', 'edit-details', '1234', '-t', '{"firstName":"Supermand"}']) @@ -252,6 +253,7 @@ def test_edit_details_bad_json(self): self.assertEqual(result.exit_code, 2) """User delete tests""" + @mock.patch('SoftLayer.CLI.user.delete.click') def test_delete(self, click): result = self.run_command(['user', 'delete', '12345']) @@ -269,3 +271,17 @@ def test_delete_failure(self, click): self.assert_no_fail(result) self.assert_called_with('SoftLayer_User_Customer', 'editObject', args=({'userStatusId': 1021},), identifier=12345) + + """User vpn manual config tests""" + + @mock.patch('SoftLayer.CLI.user.vpn_manual.click') + def test_vpn_manual(self, click): + result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) + click.secho.assert_called_with('12345 vpn manual config enable', fg='green') + self.assert_no_fail(result) + + def test_vpn_manual_fail(self): + mock = self.set_mock('SoftLayer_User_Customer', 'editObject') + mock.return_value = False + result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) + self.assert_no_fail(result) From 4d297f2a665552d736425edd87c889359223c07f Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 3 Apr 2020 18:42:46 -0400 Subject: [PATCH 0305/1582] 1239 add vpn subnet access to a user --- SoftLayer/CLI/routes.py | 3 +- SoftLayer/CLI/user/vpn_subnet.py | 30 +++++++++++ ...SoftLayer_Network_Service_Vpn_Overrides.py | 2 + SoftLayer/fixtures/SoftLayer_User_Customer.py | 9 +++- SoftLayer/managers/user.py | 52 +++++++++++++++++-- tests/CLI/modules/user_tests.py | 20 +++++++ tests/managers/user_tests.py | 20 ++++++- 7 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/CLI/user/vpn_subnet.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6d976c3f4..4a326130b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -191,11 +191,9 @@ ('loadbal:order-options', 'SoftLayer.CLI.loadbal.order:order_options'), ('loadbal:cancel', 'SoftLayer.CLI.loadbal.order:cancel'), - ('loadbal:ns-detail', 'SoftLayer.CLI.loadbal.ns_detail:cli'), ('loadbal:ns-list', 'SoftLayer.CLI.loadbal.ns_list:cli'), - ('metadata', 'SoftLayer.CLI.metadata:cli'), ('nas', 'SoftLayer.CLI.nas'), @@ -305,6 +303,7 @@ ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), ('user:vpn-manual', 'SoftLayer.CLI.user.vpn_manual:cli'), + ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), diff --git a/SoftLayer/CLI/user/vpn_subnet.py b/SoftLayer/CLI/user/vpn_subnet.py new file mode 100644 index 000000000..0ea12284d --- /dev/null +++ b/SoftLayer/CLI/user/vpn_subnet.py @@ -0,0 +1,30 @@ +"""List Users.""" +# :license: MIT, see LICENSE for more details. + + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.option('--add/--remove', default=True, + help="Add or remove access to subnets.") +@click.argument('user', nargs=1, required=True) +@click.argument('subnet', nargs=-1, required=True) +@environment.pass_env +def cli(env, user, add, subnet): + """Add or remove subnets access for a user.""" + mgr = SoftLayer.UserManager(env.client) + user_id = helpers.resolve_id(mgr.resolve_ids, user, 'username') + if add: + result = mgr.vpn_subnet_add(user_id, subnet) + else: + result = mgr.vpn_subnet_remove(user_id, subnet) + + if result: + click.secho("%s updated successfully" % (user), fg='green') + else: + click.secho("Failed to update %s" % (user), fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py b/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py new file mode 100644 index 000000000..a0c5caec2 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Service_Vpn_Overrides.py @@ -0,0 +1,2 @@ +createObjects = True +deleteObjects = True diff --git a/SoftLayer/fixtures/SoftLayer_User_Customer.py b/SoftLayer/fixtures/SoftLayer_User_Customer.py index 4b30ba326..42c8f84cc 100644 --- a/SoftLayer/fixtures/SoftLayer_User_Customer.py +++ b/SoftLayer/fixtures/SoftLayer_User_Customer.py @@ -63,7 +63,6 @@ 'name': 'Add/Upgrade Storage (StorageLayer)'} ] - getLoginAttempts = [ { "createDate": "2017-10-03T09:28:33-06:00", @@ -74,8 +73,16 @@ } ] +getOverrides = [ + { + 'id': 3661234, + 'subnetId': 1234 + } +] + addBulkPortalPermission = True removeBulkPortalPermission = True createObject = getObject editObject = True addApiAuthenticationKey = True +updateVpnUser = True diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index df2d7b454..a299d6209 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -34,6 +34,7 @@ class UserManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client self.user_service = self.client['SoftLayer_User_Customer'] + self.override_service = self.client['Network_Service_Vpn_Overrides'] self.account_service = self.client['SoftLayer_Account'] self.resolvers = [self._get_id_from_username] self.all_permissions = None @@ -268,14 +269,59 @@ def add_api_authentication_key(self, user_id): return self.user_service.addApiAuthenticationKey(id=user_id) def vpn_manual(self, user_id, value): - """Set vpnManualConfig flag + """Enable or disable the manual config of subnets. - :param int user_id: User to edit - :param bool value: Value for vpnManualConfig flag + :param int user_id: User to edit. + :param bool value: Value for vpnManualConfig flag. """ user_object = {'vpnManualConfig': value} return self.edit_user(user_id, user_object) + def vpn_subnet_add(self, user_id, subnet_ids): + """Add subnets for a user. + + :param int user_id: User to edit. + :param list subnet_ids: list of subnet Ids. + """ + overrides = [{"userId": user_id, "subnetId": subnet_id} for subnet_id in subnet_ids] + return_value = self.override_service.createObjects(overrides) + return return_value and self.user_service.updateVpnUser(id=user_id) + + def vpn_subnet_remove(self, user_id, subnet_ids): + """Remove subnets for a user. + + :param int user_id: User to edit. + :param list subnet_ids: list of subnet Ids. + """ + overrides = self.get_overrides_list(user_id, subnet_ids) + return_value = self.override_service.deleteObjects(overrides) + return return_value and self.user_service.updateVpnUser(id=user_id) + + def get_overrides_list(self, user_id, subnet_ids): + """Converts a list of subnets to a list of overrides. + + :param int user_id: The ID of the user. + :param list subnet_ids: A list of subnets. + :returns: A list of overrides associated with the given subnets. + """ + + overrides_list = [] + matching_overrides = {} + output_error = "Subnet {} does not exist in the subnets assigned for user {}" + _mask = 'mask[id,subnetId]' + overrides = self.user_service.getOverrides(id=user_id, mask=_mask) + for subnet in subnet_ids: + for override in overrides: + if int(subnet) == override.get('subnetId'): + matching_overrides = override + break + if matching_overrides.get('subnetId') is None: + raise exceptions.SoftLayerError(output_error.format(subnet, user_id)) + + overrides_list.append(matching_overrides) + + return overrides_list + def _keyname_search(haystack, needle): for item in haystack: diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index d69e36a27..6f58c14a0 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -285,3 +285,23 @@ def test_vpn_manual_fail(self): mock.return_value = False result = self.run_command(['user', 'vpn-manual', '12345', '--enable']) self.assert_no_fail(result) + + """User vpn subnet tests""" + + @mock.patch('SoftLayer.CLI.user.vpn_subnet.click') + def test_vpn_subnet_add(self, click): + result = self.run_command(['user', 'vpn-subnet', '12345', '--add', '1234']) + click.secho.assert_called_with('12345 updated successfully', fg='green') + self.assert_no_fail(result) + + def test_vpn_subnet_add_fail(self): + mock = self.set_mock('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + mock.return_value = False + result = self.run_command(['user', 'vpn-subnet', '12345', '--add', '1234']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.user.vpn_subnet.click') + def test_vpn_subnet_remove(self, click): + result = self.run_command(['user', 'vpn-subnet', '12345', '--remove', '1234']) + click.secho.assert_called_with('12345 updated successfully', fg='green') + self.assert_no_fail(result) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 66443de04..79b41e26f 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -9,7 +9,6 @@ from SoftLayer import exceptions from SoftLayer import testing - real_datetime_class = datetime.datetime @@ -18,6 +17,7 @@ def mock_datetime(target, datetime_module): https://solidgeargroup.com/mocking-the-time """ + class DatetimeSubclassMeta(type): @classmethod def __instancecheck__(mcs, obj): @@ -106,7 +106,6 @@ def test_get_logins_default(self): def test_get_events_default(self): target = datetime.datetime(2018, 5, 15) with mock_datetime(target, datetime): - self.manager.get_events(1234) expected_filter = { 'userId': { @@ -221,3 +220,20 @@ def test_create_user_handle_paas_exception(self): self.assertEqual(ex.args[0], "Your request for a new user was received, but it needs to be processed by " "the Platform Services API first. Barring any errors on the Platform Services " "side, your new user should be created shortly.") + + # def test_list_user_filter(self): + # test_filter = {'id': {'operation': 1234}} + # self.manager.list_users(objectfilter=test_filter) + # self.assert_called_with('SoftLayer_Account', 'getUsers', filter=test_filter) + + def test_vpn_manual(self): + self.manager.vpn_manual(1234, True) + self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=1234) + + def test_vpn_subnet_add(self): + self.manager.vpn_subnet_add(1234, [1234]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + + def test_vpn_subnet_remove(self): + self.manager.vpn_subnet_remove(1234, [1234]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects') From 0a7ff724221e092b1a2c7516c642f11df2cc1a11 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 6 Apr 2020 17:15:59 -0500 Subject: [PATCH 0306/1582] #1247 added account billing-items/item-details/cancel-item commands --- SoftLayer/CLI/account/billing_items.py | 60 +++++++++++++ SoftLayer/CLI/account/cancel_item.py | 18 ++++ SoftLayer/CLI/account/item_detail.py | 52 +++++++++++ SoftLayer/CLI/formatting.py | 1 + SoftLayer/CLI/routes.py | 3 + SoftLayer/fixtures/SoftLayer_Account.py | 90 +++++++++++++++++++- SoftLayer/fixtures/SoftLayer_Billing_Item.py | 64 ++++++++++++++ SoftLayer/managers/account.py | 58 +++++++++++++ SoftLayer/utils.py | 36 +++++++- tests/CLI/modules/account_tests.py | 18 ++++ tests/managers/account_tests.py | 40 +++++++++ 11 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/account/billing_items.py create mode 100644 SoftLayer/CLI/account/cancel_item.py create mode 100644 SoftLayer/CLI/account/item_detail.py diff --git a/SoftLayer/CLI/account/billing_items.py b/SoftLayer/CLI/account/billing_items.py new file mode 100644 index 000000000..32bc6c271 --- /dev/null +++ b/SoftLayer/CLI/account/billing_items.py @@ -0,0 +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 diff --git a/SoftLayer/CLI/account/cancel_item.py b/SoftLayer/CLI/account/cancel_item.py new file mode 100644 index 000000000..de0fa446b --- /dev/null +++ b/SoftLayer/CLI/account/cancel_item.py @@ -0,0 +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) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py new file mode 100644 index 000000000..7a2c53df3 --- /dev/null +++ b/SoftLayer/CLI/account/item_detail.py @@ -0,0 +1,52 @@ +"""Gets some details about a specific billing item.""" +# :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() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Gets detailed information about a billing item.""" + manager = AccountManager(env.client) + item = manager.get_billing_item(identifier) + env.fout(item_table(item)) + + +def item_table(item): + """Formats a table for billing items""" + + date_format = '%Y-%m-%d' + table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) + table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) + table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) + table.add_row(['description', item.get('description')]) + fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + if fqdn != ".": + table.add_row(['FQDN', fqdn]) + + if item.get('hourlyFlag', False): + table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) + table.add_row(['hoursUsed', item.get('hoursUsed')]) + table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) + else: + table.add_row(['recurringFee', item.get('recurringFee')]) + + ordered_by = "IBM" + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + table.add_row(['Notes', item.get('notes')]) + table.add_row(['Location', utils.lookup(item, 'location', 'name')]) + if item.get('children'): + for child in item.get('children'): + table.add_row([child.get('categoryCode'), child.get('description')]) + + return table diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b591f814f..16e4a8d85 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -62,6 +62,7 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 # responds to .separator if hasattr(data, 'separator'): + print("THERE IS A SEPARATOR |{}|".format(data.separator)) output = [format_output(d, fmt=fmt) for d in data if d] return str(SequentialOutput(data.separator, output)) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index f6edee475..b8fd294a1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -17,6 +17,9 @@ ('account:events', 'SoftLayer.CLI.account.events:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), + ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), + ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), + ('account:cancel-item', 'SoftLayer.CLI.account.cancel_item:cli'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index ddb2a4354..9524c61cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -417,7 +417,7 @@ getClosedTickets = [ticket for ticket in getTickets if ticket['statusId'] == 1002] -getCurrentUser = {'id': 12345, +getCurrentUser = {'id': 12345, 'username': 'testAccount', 'apiAuthenticationKeys': [{'authenticationKey': 'A' * 64}]} getCdnAccounts = [ @@ -852,3 +852,91 @@ } } ] + +getAllTopLevelBillingItems = [ + { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T09:53:41-06:00", + "cycleStartDate": "2020-04-03T23:12:04-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-netbase.com", + "hostName": "testsangeles101", + "id": 53891943, + "lastBillDate": "2020-04-03T23:12:04-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68626055, + "parentId": "None", + "recurringFee": "1000", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68626055, + "order": { + "id": 4544893, + "userRecord": { + "displayName": "TEst", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "id": 1001, + "keyName": "CANCEL_PENDING", + "name": "Cancel Pending" + } + } + } + }, + "resourceTableId": 544444 + }, + { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T09:56:44-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-netbase.com", + "hostName": "testsangeles101", + "id": 53892197, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68626801, + "recurringFee": "22220", + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68626801, + "order": { + "id": 4545911, + "userRecord": { + "displayName": "Test", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "id": 1001, + "keyName": "ACTIVE", + "name": "Active" + } + } + } + }, + "resourceTableId": 777777 + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Item.py b/SoftLayer/fixtures/SoftLayer_Billing_Item.py index 6bcf84493..a35e51c6b 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Item.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Item.py @@ -1,3 +1,67 @@ cancelService = True cancelServiceOnAnniversaryDate = True cancelItem = True + +getObject = { + "allowCancellationFlag": 1, + "cancellationDate": "None", + "categoryCode": "server", + "createDate": "2015-05-28T10:36:38-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "Dual E5-2690 v3 (12 Cores, 2.60 GHz)", + "domainName": "sl-test.com", + "hostName": "testsangeles101", + "id": 53897671, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "orderItemId": 68634907, + "parentId": "None", + "recurringFee": "1000", + "recurringMonths": 1, + "children": [ + { + "allowCancellationFlag": 1, + "associatedBillingItemId": "53897671", + "cancellationDate": "None", + "categoryCode": "second_processor", + "createDate": "2015-05-28T10:36:38-06:00", + "cycleStartDate": "2020-04-03T23:12:05-06:00", + "description": "E5-2690 v3 (12 Cores, 2.60 GHz)", + "id": 53897673, + "lastBillDate": "2020-04-03T23:12:05-06:00", + "modifyDate": "2020-04-03T23:12:07-06:00", + "nextBillDate": "2020-05-03T23:00:00-06:00", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 68634909, + "parentId": 53897671, + "recurringFee": "1000", + "setupFeeTaxRate": "0" + }, + ], + "hourlyFlag": False, + "location": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, + "nextInvoiceTotalRecurringAmount": 0, + "orderItem": { + "id": 68634907, + "order": { + "id": 4546175, + "userRecord": { + "displayName": "Tester", + "email": "test@us.ibm.com", + "id": 167758, + "userStatus": { + "keyName": "ACTIVE", + "name": "Active" + } + } + } + }, + "resourceTableId": "None" +} diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 1f7d4871d..4269bc7a5 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -139,3 +139,61 @@ def get_billing_items(self, identifier): iter=True, limit=100 ) + + def get_account_billing_items(self, mask=None): + """Gets all the topLevelBillingItems currently active on the account + + :param string mask: Object Mask + :return: Billing_Item + """ + + if mask is None: + mask = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag + ]""" + + object_filter = { + "allTopLevelBillingItems": { + "cancellationDate": { + "operation": "is null" + }, + "createDate": utils.query_filter_orderby() + } + } + + return self.client.call('Account', 'getAllTopLevelBillingItems', + mask=mask, filter=object_filter, iter=True, limit=100) + + def get_billing_item(self, identifier, mask=None): + """Gets details about a billing item + + :param int identifier Billing_Item id + :param string mask: Object mask to use. + :return: Billing_Item + """ + + if mask is None: + mask = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag, children + ]""" + + return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) + + def cancel_item(self, identifier, reason="No longer needed", note=None): + """Cancels a specific billing item with a reason + + :param int identifier: Billing_Item id + :param string reason: A cancellation reason + :param string note: Custom note to set when cancelling. Defaults to information about who canceled the item. + :return: bool + """ + + if note is None: + user = self.client.call('Account', 'getCurrentUser', mask="mask[id,displayName,email,username]") + note = "Cancelled by {} with the SLCLI".format(user.get('username')) + + return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0234bf72d..cc6d7bd4f 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -121,6 +121,21 @@ def query_filter_date(start, end): } +def query_filter_orderby(sort="ASC"): + """Returns an object filter operation for sorting + + :param string sort: either ASC or DESC + """ + _filter = { + "operation": "orderBy", + "options": [{ + "name": "sort", + "value": [sort] + }] + } + return _filter + + def format_event_log_date(date_string, utc): """Gets a date in the format that the SoftLayer_EventLog object likes. @@ -305,7 +320,12 @@ def clean_time(sltime, in_format='%Y-%m-%dT%H:%M:%S%z', out_format='%Y-%m-%d %H: clean = datetime.datetime.strptime(sltime, in_format) return clean.strftime(out_format) # The %z option only exists with py3.6+ - except ValueError: + except ValueError as e: + # Just ignore data that in_format didn't process. + ulr = len(e.args[0].partition('unconverted data remains: ')[2]) + if ulr: + clean = datetime.datetime.strptime(sltime[:-ulr], in_format) + return clean.strftime(out_format) return sltime @@ -334,3 +354,17 @@ def days_to_datetime(days): date -= datetime.timedelta(days=days) return date + + +def trim_to(string, length=80, tail="..."): + """Returns a string that is length long. tail added if trimmed + + :param string string: String you want to trim + :param int length: max length for the string + :param string tail: appended to strings that were trimmed. + """ + + if len(string) > length: + return string[:length] + tail + else: + return string diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index c495546c8..e231bb2be 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -93,3 +93,21 @@ def test_account_summary(self): result = self.run_command(['account', 'summary']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject') + + # slcli account billing-items + def test_account_billing_items(self): + result = self.run_command(['account', 'billing-items']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems') + + # slcli account item-detail + def test_account_get_billing_item_detail(self): + result = self.run_command(['account', 'item-detail', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier='12345') + + # slcli account cancel-item + def test_account_cancel_item(self): + result = self.run_command(['account', 'cancel-item', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 7efc42acd..513aa44ff 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -52,3 +52,43 @@ def test_get_invoices_closed(self): def test_get_billing_items(self): self.manager.get_billing_items(12345) self.assert_called_with('SoftLayer_Billing_Invoice', 'getInvoiceTopLevelItems') + + def test_get_account_billing_items(self): + self.manager.get_account_billing_items() + object_filter = { + "allTopLevelBillingItems": { + "cancellationDate": { + "operation": "is null" + }, + "createDate": { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['ASC'] + }] + } + } + } + + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems', + offset=0, limit=100, filter=object_filter) + self.manager.get_account_billing_items(mask="id") + self.assert_called_with('SoftLayer_Account', 'getAllTopLevelBillingItems', mask="mask[id]") + + def test_get_billing_item(self): + self.manager.get_billing_item(12345) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345) + self.manager.get_billing_item(12345, mask="id") + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345, mask="mask[id]") + + def test_cancel_item(self): + self.manager.cancel_item(12345) + reason = "No longer needed" + note = "Cancelled by testAccount with the SLCLI" + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', + args=(False, True, reason, note), identifier=12345) + reason = "TEST" + note = "note test" + self.manager.cancel_item(12345, reason, note) + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', + args=(False, True, reason, note), identifier=12345) From 2b0ffd9101f4aa771aba88b187ac5a8512a28739 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 7 Apr 2020 16:26:08 -0400 Subject: [PATCH 0307/1582] add command docs/cli --- 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 3cd899d4a..4fd245e22 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -36,6 +36,10 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :prog: hw detail :show-nested: +.. click:: SoftLayer.CLI.hardware.billing:cli + :prog: hw billing + :show-nested: + .. click:: SoftLayer.CLI.hardware.edit:cli :prog: hw edit diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..75db769a8 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -245,6 +245,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs usage :show-nested: +.. click:: SoftLayer.CLI.virt.billing:cli + :prog: vs billing + :show-nested: + From 620ccc8638b1d006f6668163828f03c628d38133 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 16:54:11 -0500 Subject: [PATCH 0308/1582] #1247 added docCheck.py to make sure new commands get documented --- SoftLayer/CLI/routes.py | 1 + docCheck.py | 94 +++++++++++++++++++++++++++++++++++++++++ docs/cli/account.rst | 12 ++++++ docs/cli/block.rst | 8 ++++ docs/cli/file.rst | 8 ++++ docs/cli/hardware.rst | 40 +++++++++--------- docs/cli/image.rst | 4 ++ docs/cli/ipsec.rst | 54 +++++++++++++++++++++++ docs/cli/loadbal.rst | 4 +- docs/cli/nas.rst | 12 ++++++ docs/cli/vs.rst | 46 ++++++++++++-------- tox.ini | 6 ++- 12 files changed, 249 insertions(+), 40 deletions(-) create mode 100644 docCheck.py create mode 100644 docs/cli/nas.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index b8fd294a1..5d7d89f40 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,6 +226,7 @@ ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), + ('rwhois:sho1w', 'SoftLayer.CLI.rwhois.show1:cli'), ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), diff --git a/docCheck.py b/docCheck.py new file mode 100644 index 000000000..f6b11ba36 --- /dev/null +++ b/docCheck.py @@ -0,0 +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() diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 9b3ad6954..c34f37d7d 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -22,4 +22,16 @@ Account Commands .. click:: SoftLayer.CLI.account.invoice_detail:cli :prog: account invoice-detail + :show-nested: + +.. click:: SoftLayer.CLI.account.billing_items:cli + :prog: account billing-items + :show-nested: + +.. click:: SoftLayer.CLI.account.item_detail:cli + :prog: account item-detail + :show-nested: + +.. click:: SoftLayer.CLI.account.cancel_item:cli + :prog: account cancel-item :show-nested: \ No newline at end of file diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 860872ce7..8b31d5a99 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -130,3 +130,11 @@ Block Commands .. click:: SoftLayer.CLI.block.subnets.remove:cli :prog: block subnets-remove :show-nested: + +.. click:: SoftLayer.CLI.block.refresh:cli + :prog: block volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.block.convert:cli + :prog: block volume-convert + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 13cf92a61..52dad83a4 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -110,3 +110,11 @@ File Commands .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli :prog: file snapshot-schedule-list :show-nested: + +.. click:: SoftLayer.CLI.file.refresh:cli + :prog: file volume-refresh + :show-nested: + +.. click:: SoftLayer.CLI.file.convert:cli + :prog: file volume-convert + :show-nested: \ No newline at end of file diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3cd899d4a..e99413c90 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -5,40 +5,40 @@ Interacting with Hardware .. click:: SoftLayer.CLI.hardware.bandwidth:cli - :prog: hw bandwidth + :prog: hardware bandwidth :show-nested: .. click:: SoftLayer.CLI.hardware.cancel_reasons:cli - :prog: hw cancel-reasons + :prog: hardware cancel-reasons :show-nested: .. click:: SoftLayer.CLI.hardware.cancel:cli - :prog: hw cancel + :prog: hardware cancel :show-nested: .. click:: SoftLayer.CLI.hardware.create_options:cli - :prog: hw create-options + :prog: hardware create-options :show-nested: .. click:: SoftLayer.CLI.hardware.create:cli - :prog: hw create + :prog: hardware create :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. .. click:: SoftLayer.CLI.hardware.credentials:cli - :prog: hw credentials + :prog: hardware credentials :show-nested: .. click:: SoftLayer.CLI.hardware.detail:cli - :prog: hw detail + :prog: hardware detail :show-nested: .. click:: SoftLayer.CLI.hardware.edit:cli - :prog: hw edit + :prog: hardware edit :show-nested: **Note :** Using multiple ' **:** ' can cause an error. @@ -53,55 +53,55 @@ Provides some basic functionality to order a server. `slcli order` has a more fu When setting port speed, use "-1" to indicate best possible configuration. Using 10/100/1000/10000 on a server with a redundant interface may result the interface entering a degraded state. See `setPublicNetworkInterfaceSpeed `_ for more information. .. click:: SoftLayer.CLI.hardware.list:cli - :prog: hw list + :prog: hardware list :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_cycle - :prog: hw power-cycle + :prog: hardware power-cycle :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_off - :prog: hw power-off + :prog: hardware power-off :show-nested: .. click:: SoftLayer.CLI.hardware.power:power_on - :prog: hw power-on + :prog: hardware power-on :show-nested: .. click:: SoftLayer.CLI.hardware.power:reboot - :prog: hw reboot + :prog: hardware reboot :show-nested: .. click:: SoftLayer.CLI.hardware.reload:cli - :prog: hw reload + :prog: hardware reload :show-nested: .. click:: SoftLayer.CLI.hardware.power:rescue - :prog: hw rescue + :prog: hardware rescue .. click:: SoftLayer.CLI.hardware.reflash_firmware:cli - :prog: hw reflash-firmware + :prog: hardware reflash-firmware :show-nested: Reflash here means the current version of the firmware running on your server will be re-flashed onto the selected hardware. This does require a reboot. See `slcli hw update-firmware` if you want the newest version. .. click:: SoftLayer.CLI.hardware.update_firmware:cli - :prog: hw update-firmware + :prog: hardware update-firmware :show-nested: This function updates the firmware of a server. If already at the latest version, no software is installed. .. click:: SoftLayer.CLI.hardware.toggle_ipmi:cli - :prog: hw toggle-ipmi + :prog: hardware toggle-ipmi :show-nested: .. click:: SoftLayer.CLI.hardware.ready:cli - :prog: hw ready + :prog: hardware ready :show-nested: .. click:: SoftLayer.CLI.hardware.dns:cli - :prog: hw dns-sync + :prog: hardware dns-sync :show-nested: diff --git a/docs/cli/image.rst b/docs/cli/image.rst index 771abd16c..93ba13321 100644 --- a/docs/cli/image.rst +++ b/docs/cli/image.rst @@ -26,3 +26,7 @@ Disk Image Commands .. click:: SoftLayer.CLI.image.export:cli :prog: image export :show-nested: + +.. click:: SoftLayer.CLI.image.datacenter:cli + :prog: image datacenter + :show-nested: diff --git a/docs/cli/ipsec.rst b/docs/cli/ipsec.rst index 2786a5ed0..cc1ed5b11 100644 --- a/docs/cli/ipsec.rst +++ b/docs/cli/ipsec.rst @@ -14,6 +14,12 @@ To see more information about the IPSEC tunnel context module and API internacti ipsec list ---------- + + +.. click:: SoftLayer.CLI.vpn.ipsec.list:cli + :prog: ipsec list + :show-nested: + A list of all IPSEC tunnel contexts associated with the current user's account can be retrieved via the ``ipsec list`` command. This provides a brief overview of all tunnel contexts and can be used to retrieve an individual context's identifier, which all other CLI commands require. :: @@ -28,6 +34,12 @@ A list of all IPSEC tunnel contexts associated with the current user's account c ipsec detail ------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.detail:cli + :prog: ipsec detail + :show-nested: + + More detailed information can be retrieved for an individual context using the ``ipsec detail`` command. Using the detail command, information about associated internal subnets, remote subnets, static subnets, service subnets and address translations may also be retrieved using multiple instances of the ``-i|--include`` option. :: @@ -91,6 +103,12 @@ More detailed information can be retrieved for an individual context using the ` ipsec update ------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.update:cli + :prog: ipsec update + :show-nested: + + Most values listed in the tunnel context detail printout can be modified using the ``ipsec update`` command. The following is given when executing with the ``-h|--help`` option and highlights all properties that may be modified. :: @@ -134,6 +152,12 @@ Most values listed in the tunnel context detail printout can be modified using t ipsec configure --------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.configure:cli + :prog: ipsec configure + :show-nested: + + A request to configure SoftLayer network devices for a given tunnel context can be issued using the ``ipsec configure`` command. .. note:: @@ -144,6 +168,12 @@ A request to configure SoftLayer network devices for a given tunnel context can ipsec subnet-add ---------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.subnet.add:cli + :prog: ipsec subnet-add + :show-nested: + + Internal, remote and service subnets can be associated to an IPSEC tunnel context using the ``ipsec subnet-add`` command. Additionally, remote subnets can be created using this same command, which will then be associated to the targeted tunnel context. .. note:: @@ -167,6 +197,13 @@ The following is an example of creating and associating a remote subnet to a tun ipsec subnet-remove ------------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.subnet.remove:cli + :prog: ipsec subnet-remove + :show-nested: + + + Internal, remote and service subnets can be disassociated from an IPSEC tunnel context via the ``ipsec subnet-remove`` command. .. note:: @@ -183,6 +220,12 @@ The following is an example of disassociating an internal subnet from a tunnel c ipsec translation-add --------------------- + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.add:cli + :prog: ipsec translation-add + :show-nested: + + Address translation entries can be added to a tunnel context to provide NAT functionality from a statically routed subnet associated with the tunnel context to a remote subnet. This action is performed with the ``ipsec translation-add`` command. .. note:: @@ -199,6 +242,12 @@ The following is an example of adding a new address translation entry. ipsec translation-remove ------------------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.remove:cli + :prog: ipsec translation-remove + :show-nested: + + Address translation entries can be removed using the ``ipsec translation-remove`` command. The following is an example of removing an address translation entry. @@ -211,6 +260,11 @@ The following is an example of removing an address translation entry. ipsec translation-update ------------------------ + +.. click:: SoftLayer.CLI.vpn.ipsec.translation.update:cli + :prog: ipsec translation-update + :show-nested: + Address translation entries may also be modified using the ``ipsec translation-update`` command. The following is an example of updating an existing address translation entry. diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index cec4200fd..a4116b877 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -23,7 +23,7 @@ LBaaS Commands :prog: loadbal member-add :show-nested: .. click:: SoftLayer.CLI.loadbal.members:remove - :prog: loadbal member-remove + :prog: loadbal member-del :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:add :prog: loadbal pool-add @@ -32,7 +32,7 @@ LBaaS Commands :prog: loadbal pool-edit :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:delete - :prog: loadbal pool-delete + :prog: loadbal pool-del :show-nested: .. click:: SoftLayer.CLI.loadbal.pools:l7pool_add :prog: loadbal l7pool-add diff --git a/docs/cli/nas.rst b/docs/cli/nas.rst new file mode 100644 index 000000000..024744919 --- /dev/null +++ b/docs/cli/nas.rst @@ -0,0 +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 + :show-nested: \ No newline at end of file diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..96bb5a9d2 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -173,79 +173,91 @@ username is 'root' and password is 'ABCDEFGH'. .. click:: SoftLayer.CLI.virt.bandwidth:cli - :prog: vs bandwidth + :prog: virtual bandwidth :show-nested: If no timezone is specified, IMS local time (CST) will be assumed, which might not match your user's selected timezone. .. click:: SoftLayer.CLI.virt.cancel:cli - :prog: vs cancel + :prog: virtual cancel :show-nested: .. click:: SoftLayer.CLI.virt.capture:cli - :prog: vs capture + :prog: virtual capture :show-nested: .. click:: SoftLayer.CLI.virt.create:cli - :prog: vs create + :prog: virtual create :show-nested: .. click:: SoftLayer.CLI.virt.create_options:cli - :prog: vs create-options + :prog: virtual create-options :show-nested: .. click:: SoftLayer.CLI.virt.dns:cli - :prog: vs dns-sync + :prog: virtual dns-sync :show-nested: .. click:: SoftLayer.CLI.virt.edit:cli - :prog: vs edit + :prog: virtual edit :show-nested: .. click:: SoftLayer.CLI.virt.list:cli - :prog: vs list + :prog: virtual list :show-nested: .. click:: SoftLayer.CLI.virt.power:pause - :prog: vs pause + :prog: virtual pause :show-nested: .. click:: SoftLayer.CLI.virt.power:power_on - :prog: vs power-on + :prog: virtual power-on :show-nested: .. click:: SoftLayer.CLI.virt.power:power_off - :prog: vs power-off + :prog: virtual power-off :show-nested: .. click:: SoftLayer.CLI.virt.power:resume - :prog: vs resume + :prog: virtual resume :show-nested: .. click:: SoftLayer.CLI.virt.power:rescue - :prog: vs rescue + :prog: virtual rescue :show-nested: .. click:: SoftLayer.CLI.virt.power:reboot - :prog: vs reboot + :prog: virtual reboot :show-nested: .. click:: SoftLayer.CLI.virt.ready:cli - :prog: vs ready + :prog: virtual ready :show-nested: .. click:: SoftLayer.CLI.virt.upgrade:cli - :prog: vs upgrade + :prog: virtual upgrade :show-nested: .. click:: SoftLayer.CLI.virt.usage:cli - :prog: vs usage + :prog: virtual usage :show-nested: +.. click:: SoftLayer.CLI.virt.detail:cli + :prog: virtual detail + :show-nested: + + +.. click:: SoftLayer.CLI.virt.reload:cli + :prog: virtual reload + :show-nested: + +.. click:: SoftLayer.CLI.virt.credentials:cli + :prog: virtual credentials + :show-nested: Reserved Capacity diff --git a/tox.ini b/tox.ini index 22af57229..c9b3b5e72 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,pypy3,analysis,coverage +envlist = py35,py36,py37,py38,pypy3,analysis,coverage,docs [flake8] @@ -57,3 +57,7 @@ commands = --min-similarity-lines=50 \ --max-line-length=120 \ -r n + +[testenv:docs] +commands = + python ./docCheck.py \ No newline at end of file From 0759c7d426fbfe212e230598264c2d6ace82af12 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 16:55:43 -0500 Subject: [PATCH 0309/1582] removed a bad route --- SoftLayer/CLI/routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 5d7d89f40..b8fd294a1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,7 +226,6 @@ ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), - ('rwhois:sho1w', 'SoftLayer.CLI.rwhois.show1:cli'), ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), From 5970edbf1ea8bc1b73c4d2655eb789ff11d8373b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 17:14:34 -0500 Subject: [PATCH 0310/1582] added a github workflow to see how it works --- .github/workflows/documentation.yml | 27 +++++++++++++++++++++++++++ tools/test-requirements.txt | 1 + 2 files changed, 28 insertions(+) create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 000000000..424eb7c26 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,27 @@ +name: softlayer + +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/tools/test-requirements.txt b/tools/test-requirements.txt index 3080abf43..d417e04c1 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -1,4 +1,5 @@ tox +coveralls pytest pytest-cov mock From 5eced1617c8c2557632435796ece74965b8f6ce7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 7 Apr 2020 17:27:39 -0500 Subject: [PATCH 0311/1582] removed debug test --- SoftLayer/CLI/formatting.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 16e4a8d85..b591f814f 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -62,7 +62,6 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 # responds to .separator if hasattr(data, 'separator'): - print("THERE IS A SEPARATOR |{}|".format(data.separator)) output = [format_output(d, fmt=fmt) for d in data if d] return str(SequentialOutput(data.separator, output)) From 0a325133b4cedc332a8da786a225b7e1275938cc Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 12:05:23 -0400 Subject: [PATCH 0312/1582] #1239 fix cli user docs --- SoftLayer/CLI/user/vpn_manual.py | 4 ++-- SoftLayer/CLI/user/vpn_subnet.py | 2 +- docs/cli/users.rst | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/user/vpn_manual.py b/SoftLayer/CLI/user/vpn_manual.py index fb8ac78fc..35e92f758 100644 --- a/SoftLayer/CLI/user/vpn_manual.py +++ b/SoftLayer/CLI/user/vpn_manual.py @@ -1,4 +1,4 @@ -"""List Users.""" +"""Enable or Disable vpn subnets manual config for a user.""" # :license: MIT, see LICENSE for more details. @@ -12,7 +12,7 @@ @click.command() @click.argument('user') @click.option('--enable/--disable', default=True, - help="Whether enable or disable vpnManualConfig flag.") + help="Enable or disable vpn subnets manual config.") @environment.pass_env def cli(env, user, enable): """Enable or disable user vpn subnets manual config""" diff --git a/SoftLayer/CLI/user/vpn_subnet.py b/SoftLayer/CLI/user/vpn_subnet.py index 0ea12284d..627d58542 100644 --- a/SoftLayer/CLI/user/vpn_subnet.py +++ b/SoftLayer/CLI/user/vpn_subnet.py @@ -1,4 +1,4 @@ -"""List Users.""" +"""Add or remove specific subnets access for a user.""" # :license: MIT, see LICENSE for more details. diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 5058d0652..21ff1b7b8 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -32,4 +32,12 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user delete :show-nested: +.. click:: SoftLayer.CLI.user.vpn-manual:cli + :prog: user vpn-manual + :show-nested: + +.. click:: SoftLayer.CLI.user.vpn-subnet:cli + :prog: user vpn-subnet + :show-nested: + From ce8b5f1c713b246d9d63db386d8cec232e4b07ec Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 12:33:04 -0400 Subject: [PATCH 0313/1582] #1239 improve methods add remove subnet from user --- SoftLayer/managers/user.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index a299d6209..5875d76a8 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -285,7 +285,10 @@ def vpn_subnet_add(self, user_id, subnet_ids): """ overrides = [{"userId": user_id, "subnetId": subnet_id} for subnet_id in subnet_ids] return_value = self.override_service.createObjects(overrides) - return return_value and self.user_service.updateVpnUser(id=user_id) + update_success = self.user_service.updateVpnUser(id=user_id) + if not update_success: + raise exceptions.SoftLayerAPIError("Overrides created, but unable to update VPN user") + return return_value def vpn_subnet_remove(self, user_id, subnet_ids): """Remove subnets for a user. @@ -295,7 +298,10 @@ def vpn_subnet_remove(self, user_id, subnet_ids): """ overrides = self.get_overrides_list(user_id, subnet_ids) return_value = self.override_service.deleteObjects(overrides) - return return_value and self.user_service.updateVpnUser(id=user_id) + update_success = self.user_service.updateVpnUser(id=user_id) + if not update_success: + raise exceptions.SoftLayerAPIError("Overrides deleted, but unable to update VPN user") + return return_value def get_overrides_list(self, user_id, subnet_ids): """Converts a list of subnets to a list of overrides. From 423eda1495e63e7420d43483caf663428839830c Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 8 Apr 2020 16:00:56 -0400 Subject: [PATCH 0314/1582] #1239 fix user manager subnet tests --- tests/managers/user_tests.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 79b41e26f..b75a5a772 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -221,19 +221,28 @@ def test_create_user_handle_paas_exception(self): "the Platform Services API first. Barring any errors on the Platform Services " "side, your new user should be created shortly.") - # def test_list_user_filter(self): - # test_filter = {'id': {'operation': 1234}} - # self.manager.list_users(objectfilter=test_filter) - # self.assert_called_with('SoftLayer_Account', 'getUsers', filter=test_filter) - def test_vpn_manual(self): - self.manager.vpn_manual(1234, True) - self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=1234) + user_id = 1234 + self.manager.vpn_manual(user_id, True) + self.assert_called_with('SoftLayer_User_Customer', 'editObject', identifier=user_id) def test_vpn_subnet_add(self): - self.manager.vpn_subnet_add(1234, [1234]) - self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects') + user_id = 1234 + subnet_id = 1234 + expected_args = ( + [{"userId": user_id, "subnetId": subnet_id}], + ) + self.manager.vpn_subnet_add(user_id, [subnet_id]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects', args=expected_args) + self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) def test_vpn_subnet_remove(self): - self.manager.vpn_subnet_remove(1234, [1234]) - self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects') + user_id = 1234 + subnet_id = 1234 + overrides = [{'id': 3661234, 'subnetId': subnet_id}] + expected_args = ( + overrides, + ) + self.manager.vpn_subnet_remove(user_id, [subnet_id]) + self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) + self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) From bfa4731c38a625153bd76ebdbfca740fdc6b0394 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 8 Apr 2020 16:02:10 -0400 Subject: [PATCH 0315/1582] Add docs for vs and hardware storage. --- docs/cli/hardware.rst | 4 ++++ docs/cli/vs.rst | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 3cd899d4a..fdedad4f6 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -105,3 +105,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.dns:cli :prog: hw dns-sync :show-nested: + +.. click:: SoftLayer.CLI.hardware.storage:cli + :prog: hw storage + :show-nested: diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index afa0f8b2d..70b5631c5 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -245,6 +245,9 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: vs usage :show-nested: +.. click:: SoftLayer.CLI.virt.storage:cli + :prog: vs storage + :show-nested: From 788b7a0957c47c4e22329db9337bcac89ff34240 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 8 Apr 2020 16:15:13 -0400 Subject: [PATCH 0316/1582] Fix tox analysis. --- SoftLayer/CLI/hardware/storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/storage.py b/SoftLayer/CLI/hardware/storage.py index 46cb30be8..9af2f387c 100644 --- a/SoftLayer/CLI/hardware/storage.py +++ b/SoftLayer/CLI/hardware/storage.py @@ -45,13 +45,13 @@ def cli(env, identifier): table_hard_drives = formatting.Table(['Type', 'Name', 'Capacity', 'Serial #'], title="Other storage details") for drives in hard_drives: - type = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] + type_drive = drives['hardwareComponentModel']['hardwareGenericComponentModel']['hardwareComponentType']['type'] name = drives['hardwareComponentModel']['manufacturer'] + " " + drives['hardwareComponentModel']['name'] capacity = str(drives['hardwareComponentModel']['hardwareGenericComponentModel']['capacity']) + " " + str( drives['hardwareComponentModel']['hardwareGenericComponentModel']['units']) serial = drives['serialNumber'] - table_hard_drives.add_row([type, name, capacity, serial]) + table_hard_drives.add_row([type_drive, name, capacity, serial]) env.fout(table_credentials) env.fout(table_iscsi) From 16ad4e0972a7c5cc25778d4ee19366dd5a112bee Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 8 Apr 2020 16:15:44 -0400 Subject: [PATCH 0317/1582] fix the problems with tox --- SoftLayer/CLI/hardware/billing.py | 2 +- tests/CLI/modules/server_tests.py | 21 +++++++++------------ tests/CLI/modules/vs/vs_tests.py | 23 +++++++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 03b32e7f6..55c68c485 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -32,7 +32,7 @@ def cli(env, identifier): price_table = formatting.Table(['Item', 'Recurring Price']) for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['Description'], item['NextInvoiceTotalRecurringAmount']]) + price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) env.fout(table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 16af3a235..588ed4d1e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -830,18 +830,15 @@ def test_dns_sync_misc_exception(self, confirm_mock): def test_billing(self): result = self.run_command(['hw', 'billing', '123456']) billing_json = { - "hardwareId": "123456", - "BillingIttem": 6327, - "recurringFee": 1.54, - "Total": 16.08, - "provisionDate": None, - "prices": [ - { - "Item": "test", - "Recurring Price": 1 - } - ] + 'Billing Item Id': 6327, + 'Id': '123456', + 'Provision Date': None, + 'Recurring Fee': 1.54, + 'Total': 16.08, + 'prices': [{ + 'Item': 'test', + 'Recurring Price': 1 + }] } - self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index e82bf4e87..9fbe699c2 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -744,15 +744,18 @@ def test_bandwidth_vs_quite(self): def test_billing(self): result = self.run_command(['vs', 'billing', '123456']) vir_billing = { - "BillingIttem": 6327, - "Total": 1.54, - "VirtuallId": "123456", - "prices": [{"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}, - {"Recurring Price": 1}], - "provisionDate": None, - "recurringFee": None} + 'Billing Item Id': 6327, + 'Id': '123456', + 'Provision Date': None, + 'Recurring Fee': None, + 'Total': 1.54, + 'prices': [ + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1}, + {'Recurring Price': 1} + ] + } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), vir_billing) From f5fd7298eab6d1f2b70b4c7f16c4d4104987a290 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 8 Apr 2020 16:42:40 -0400 Subject: [PATCH 0318/1582] fix the Christopher code review --- SoftLayer/CLI/ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index b08663322..ebeee3bb3 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -66,6 +66,6 @@ def get_ticket_results(mgr, ticket_id, is_json, update_count=1): wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) if is_json: if '\n' in wrapped_entry: - wrapped_entry = re.sub(r"(? Date: Mon, 13 Apr 2020 16:00:54 -0400 Subject: [PATCH 0319/1582] fix the travis error --- SoftLayer/CLI/ticket/__init__.py | 7 +++---- tests/CLI/modules/ticket_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index ebeee3bb3..a8ffae6e0 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID @@ -64,8 +64,7 @@ def get_ticket_results(mgr, ticket_id, is_json, update_count=1): # NOTE(kmcdonald): Windows new-line characters need to be stripped out wrapped_entry += click.wrap_text(update['entry'].replace('\r', '')) - if is_json: - if '\n' in wrapped_entry: - wrapped_entry = re.sub(r"(? Date: Mon, 13 Apr 2020 16:39:56 -0400 Subject: [PATCH 0320/1582] fix the travis error --- SoftLayer/CLI/ticket/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index a8ffae6e0..81c248322 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json = False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID From 47b03f304c9873ab0e94d0453175f9be8d658210 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Tue, 14 Apr 2020 11:22:40 -0400 Subject: [PATCH 0321/1582] fix the doCheck.py --- docs/cli/hardware.rst | 4 ++-- docs/cli/users.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index cd97c9d0d..0cce23042 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -37,7 +37,7 @@ Provides some basic functionality to order a server. `slcli order` has a more fu :show-nested: .. click:: SoftLayer.CLI.hardware.billing:cli - :prog: hw billing + :prog: hardware billing :show-nested: @@ -111,5 +111,5 @@ This function updates the firmware of a server. If already at the latest version :show-nested: .. click:: SoftLayer.CLI.hardware.storage:cli - :prog: hw storage + :prog: hardware storage :show-nested: diff --git a/docs/cli/users.rst b/docs/cli/users.rst index 21ff1b7b8..feb94e352 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -32,11 +32,11 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user delete :show-nested: -.. click:: SoftLayer.CLI.user.vpn-manual:cli +.. click:: SoftLayer.CLI.user.vpn_manual:cli :prog: user vpn-manual :show-nested: -.. click:: SoftLayer.CLI.user.vpn-subnet:cli +.. click:: SoftLayer.CLI.user.vpn_subnet:cli :prog: user vpn-subnet :show-nested: From a714a1129dc5236b0ddc24dc95fecfdf2d822255 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:28:51 -0500 Subject: [PATCH 0322/1582] Adding more github action tests, removing travis CI tests --- .github/workflows/documentation.yml | 2 +- .github/workflows/tests.yml | 54 +++++++++++++++++++++++++++++ .travis.yml | 27 --------------- SoftLayer/CLI/ticket/__init__.py | 4 +-- SoftLayer/CLI/ticket/detail.py | 2 +- 5 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 424eb7c26..c713212ee 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,4 +1,4 @@ -name: softlayer +name: documentation on: push: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..d2cab7484 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,54 @@ +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5,3.6,3.7,3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e coverage + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox + run: tox -e analysis \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 35c987a63..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -# https://docs.travis-ci.com/user/languages/python/#python-37-and-higher -dist: bionic -language: python -sudo: false -matrix: - include: - - python: "3.5" - env: TOX_ENV=py35 - - python: "3.6" - env: TOX_ENV=py36 - - python: "3.7" - env: TOX_ENV=py37 - - python: "3.8" - env: TOX_ENV=py38 - - python: "pypy3" - env: TOX_ENV=pypy3 - - python: "3.6" - env: TOX_ENV=analysis - - python: "3.6" - env: TOX_ENV=coverage -install: - - pip install tox - - pip install coveralls -script: - - tox -e $TOX_ENV -after_success: - - if [[ $TOX_ENV = "coverage" ]]; then coveralls; fi diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py index 81c248322..3aec9744b 100644 --- a/SoftLayer/CLI/ticket/__init__.py +++ b/SoftLayer/CLI/ticket/__init__.py @@ -1,7 +1,7 @@ """Support tickets.""" +import re import click -import re from SoftLayer.CLI import formatting @@ -17,7 +17,7 @@ ] -def get_ticket_results(mgr, ticket_id, is_json = False, update_count=1): +def get_ticket_results(mgr, ticket_id, is_json=False, update_count=1): """Get output about a ticket. :param integer id: the ticket ID diff --git a/SoftLayer/CLI/ticket/detail.py b/SoftLayer/CLI/ticket/detail.py index a7c59416e..fad8a644a 100644 --- a/SoftLayer/CLI/ticket/detail.py +++ b/SoftLayer/CLI/ticket/detail.py @@ -21,7 +21,7 @@ def cli(env, identifier, count): """Get details for a ticket.""" is_json = False - if (env.format == 'json'): + if env.format == 'json': is_json = True mgr = SoftLayer.TicketManager(env.client) From 49ad0562743031281816561121e15a847ffd5b5c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:31:57 -0500 Subject: [PATCH 0323/1582] fixed a typo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d2cab7484..7351b5c51 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - name: Tox - run: tox -e + run: tox -e py coverage: runs-on: ubuntu-latest steps: From 91ae1ca0823a4179b0514b2c8da773adc007c647 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:38:48 -0500 Subject: [PATCH 0324/1582] Fixed test names --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7351b5c51..7aa408ed8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Test run: tox -e py coverage: runs-on: ubuntu-latest @@ -36,7 +36,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Coverage run: tox -e coverage analysis: runs-on: ubuntu-latest @@ -50,5 +50,5 @@ jobs: run: | python -m pip install --upgrade pip pip install -r tools/test-requirements.txt - - name: Tox + - name: Tox Analysis run: tox -e analysis \ No newline at end of file From 37eb8780a36826e8e0bd306e420991d5c5aa31f4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 14 Apr 2020 14:41:51 -0500 Subject: [PATCH 0325/1582] updated readme --- README.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 535b54597..b4a321866 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,10 @@ SoftLayer API Python Client =========================== -.. image:: https://travis-ci.org/softlayer/softlayer-python.svg?branch=master - :target: https://travis-ci.org/softlayer/softlayer-python +.. image:: https://github.com/softlayer/softlayer-python/workflows/Tests/badge.svg + :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3ATests + +.. image:: https://github.com/softlayer/softlayer-python/workflows/documentation/badge.svg + :target: https://github.com/softlayer/softlayer-python/actions?query=workflow%3Adocumentation .. image:: https://landscape.io/github/softlayer/softlayer-python/master/landscape.svg :target: https://landscape.io/github/softlayer/softlayer-python/master From 5fd9fb46ffde51791f5237ab92724c8913dab436 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Apr 2020 17:41:17 -0400 Subject: [PATCH 0326/1582] #1258 add control for none type value --- SoftLayer/CLI/formatting.py | 13 +++++++++---- tests/CLI/helper_tests.py | 9 +++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b591f814f..b88fe056b 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -416,11 +416,13 @@ def _format_list(result): if not result: return result - if isinstance(result[0], dict): - return _format_list_objects(result) + new_result = [item for item in result if item] + + if isinstance(new_result[0], dict): + return _format_list_objects(new_result) table = Table(['value']) - for item in result: + for item in new_result: table.add_row([iter_to_table(item)]) return table @@ -430,12 +432,15 @@ def _format_list_objects(result): all_keys = set() for item in result: - all_keys = all_keys.union(item.keys()) + if isinstance(item, dict): + all_keys = all_keys.union(item.keys()) all_keys = sorted(all_keys) table = Table(all_keys) for item in result: + if not item: + continue values = [] for key in all_keys: value = iter_to_table(item.get(key)) diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index 5c6656c21..c22278c34 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -439,11 +439,10 @@ def test_template_options(self): class TestExportToTemplate(testing.TestCase): def test_export_to_template(self): - if(sys.platform.startswith("win")): + if (sys.platform.startswith("win")): self.skipTest("Test doesn't work in Windows") # Tempfile creation is wonky on windows with tempfile.NamedTemporaryFile() as tmp: - template.export_to_template(tmp.name, { 'os': None, 'datacenter': 'ams01', @@ -487,3 +486,9 @@ def test_format_api_list_non_objects(self): self.assertIsInstance(result, formatting.Table) self.assertEqual(result.columns, ['value']) self.assertEqual(result.rows, [['a'], ['b'], ['c']]) + + def test_format_api_list_with_none_value(self): + result = formatting._format_list([{'key': [None, 'value']}, None]) + + self.assertIsInstance(result, formatting.Table) + self.assertEqual(result.columns, ['key']) From 5aaee862180da3da14d7dfbfd55370d3d5ea0138 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Apr 2020 17:56:05 -0400 Subject: [PATCH 0327/1582] add the rebundant/degraded option --- SoftLayer/CLI/hardware/edit.py | 18 +++++++++++++++--- SoftLayer/managers/hardware.py | 6 +++--- tests/CLI/modules/server_tests.py | 6 ++++-- tests/managers/hardware_tests.py | 8 ++++---- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index e4aca4dcc..c47f565f0 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,8 +22,10 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") +@click.option('--rebundant', is_flag=True,default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--degraded', is_flag=True,default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env -def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed): +def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): """Edit hardware details.""" if userdata and userfile: @@ -51,7 +53,17 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed raise exceptions.CLIAbort("Failed to update hardware") if public_speed is not None: - mgr.change_port_speed(hw_id, True, int(public_speed)) + if rebundant: + mgr.change_port_speed(hw_id, True, int(public_speed), 'rebundant') + if degraded: + mgr.change_port_speed(hw_id, True, int(public_speed), 'degraded') + if not rebundant and not degraded: + raise exceptions.CLIAbort("Failed to update hardwar") if private_speed is not None: - mgr.change_port_speed(hw_id, False, int(private_speed)) + if rebundant: + mgr.change_port_speed(hw_id, False, int(private_speed), 'rebundant') + if degraded: + mgr.change_port_speed(hw_id, False, int(private_speed), 'degraded') + if not rebundant and not degraded: + raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 99ab45b9c..e46674bf2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -297,7 +297,7 @@ def rescue(self, hardware_id): """ return self.hardware.bootToRescueLayer(id=hardware_id) - def change_port_speed(self, hardware_id, public, speed): + def change_port_speed(self, hardware_id, public, speed, rebundant): """Allows you to change the port speed of a server's NICs. :param int hardware_id: The ID of the server @@ -319,11 +319,11 @@ def change_port_speed(self, hardware_id, public, speed): if public: return self.client.call('Hardware_Server', 'setPublicNetworkInterfaceSpeed', - speed, id=hardware_id) + [rebundant, speed], id=hardware_id) else: return self.client.call('Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - speed, id=hardware_id) + [rebundant, speed], id=hardware_id) def place_order(self, **kwargs): """Places an order for a piece of hardware. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 44e2bc81b..25cb20de4 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -526,7 +526,9 @@ def test_edit(self): '--tag=dev', '--tag=green', '--public-speed=10', + '--rebundant', '--private-speed=100', + '--degraded', '100']) self.assert_no_fail(result) @@ -544,12 +546,12 @@ def test_edit(self): ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', - args=(10,), + args=(['rebundant', 10],), identifier=100, ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - args=(100,), + args=(['degraded', 100],), identifier=100, ) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9ac63c224..472e25c75 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -335,20 +335,20 @@ def test_cancel_running_transaction(self): 12345) def test_change_port_speed_public(self): - self.hardware.change_port_speed(2, True, 100) + self.hardware.change_port_speed(2, True, 100, 'degraded') self.assert_called_with('SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', identifier=2, - args=(100,)) + args=(['degraded', 100],)) def test_change_port_speed_private(self): - self.hardware.change_port_speed(2, False, 10) + self.hardware.change_port_speed(2, False, 10, 'rebundant') self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=(10,)) + args=(['rebundant', 10],)) def test_edit_meta(self): # Test editing user data From d8552baa9653f275f0af964bc4a42335efc5760f Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Mon, 27 Apr 2020 18:08:20 -0400 Subject: [PATCH 0328/1582] fix the tox analysis --- SoftLayer/CLI/hardware/edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index c47f565f0..aad0b68b4 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,8 +22,8 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") -@click.option('--rebundant', is_flag=True,default=False, help="The desired state of redundancy for the interface(s)") -@click.option('--degraded', is_flag=True,default=False, help="The desired state of degraded for the interface(s)") +@click.option('--rebundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--degraded', is_flag=True, default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): """Edit hardware details.""" From 15791619c9454d8807a4ca982a7b8099a0cd1d37 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 12:24:16 -0400 Subject: [PATCH 0329/1582] Add Account planned, unplanned and announcement events. --- SoftLayer/CLI/account/events.py | 88 ++++++++++++++++++++++++++------- SoftLayer/managers/account.py | 56 +++++++++++++++++---- tests/managers/account_tests.py | 45 ++++++++++++++++- 3 files changed, 158 insertions(+), 31 deletions(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 5cc91144d..c80614969 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -2,10 +2,10 @@ # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager -from SoftLayer import utils @click.command() @@ -16,39 +16,89 @@ def cli(env, ack_all): """Summary and acknowledgement of upcoming and ongoing maintenance events""" manager = AccountManager(env.client) - events = manager.get_upcoming_events() + planned_events = manager.get_upcoming_events("PLANNED") + unplanned_events = manager.get_upcoming_events("UNPLANNED_INCIDENT") + 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)) + + +def add_ack_flag(events, manager, ack_all): if ack_all: for event in events: result = manager.ack_event(event['id']) event['acknowledgedFlag'] = result - env.fout(event_table(events)) -def event_table(events): +def planned_event_table(events): """Formats a table for events""" - table = formatting.Table([ - "Id", - "Start Date", - "End Date", - "Subject", - "Status", - "Acknowledged", - "Updates", - "Impacted Resources" - ], title="Upcoming Events") - table.align['Subject'] = 'l' - table.align['Impacted Resources'] = 'l' + planned_table = formatting.Table(['Event Data', 'Id', 'Event ID', 'Subject', 'Status', 'Items', 'Start Date', + 'End Date', 'Acknowledged', 'Updates'], title="Planned Events") + planned_table.align['Subject'] = 'l' + planned_table.align['Impacted Resources'] = 'l' for event in events: - table.add_row([ + planned_table.add_row([ + utils.clean_time(event.get('startDate')), event.get('id'), + event.get('systemTicketId'), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), + utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), utils.clean_time(event.get('startDate')), utils.clean_time(event.get('endDate')), + event.get('acknowledgedFlag'), + event.get('updateCount'), + + ]) + return planned_table + + +def unplanned_event_table(events): + """Formats a table for events""" + unplanned_table = formatting.Table(['Id', 'Event ID', 'Subject', 'Status', 'Items', 'Start Date', + 'Last Updated', 'Acknowledged', 'Updates'], title="Unplanned Events") + unplanned_table.align['Subject'] = 'l' + unplanned_table.align['Impacted Resources'] = 'l' + for event in events: + print(event.get('modifyDate')) + unplanned_table.add_row([ + event.get('id'), + event.get('systemTicketId'), # Some subjects can have \r\n for some reason. utils.clean_splitlines(event.get('subject')), utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), + utils.clean_time(event.get('startDate')), + utils.clean_time(event.get('modifyDate')), event.get('acknowledgedFlag'), event.get('updateCount'), - event.get('impactedResourceCount') ]) - return table + return unplanned_table + + +def announcement_event_table(events): + """Formats a table for events""" + announcement_table = formatting.Table( + ['Id', 'Event ID', 'Subject', 'Status', 'Items', 'Acknowledged', 'Updates'], title="Announcement Events") + announcement_table.align['Subject'] = 'l' + announcement_table.align['Impacted Resources'] = 'l' + for event in events: + announcement_table.add_row([ + event.get('id'), + event.get('systemTicketId'), + # Some subjects can have \r\n for some reason. + utils.clean_splitlines(event.get('subject')), + utils.lookup(event, 'statusCode', 'name'), + event.get('impactedResourceCount'), + event.get('acknowledgedFlag'), + event.get('updateCount') + ]) + return announcement_table diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 4269bc7a5..56f951c28 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -47,25 +47,61 @@ def get_summary(self): """ return self.client.call('Account', 'getObject', mask=mask) - def get_upcoming_events(self): - """Retreives a list of Notification_Occurrence_Events that have not ended yet + def get_upcoming_events(self, event_type): + """Retrieves a list of Notification_Occurrence_Events that have not ended yet + :param: String event_type: notification event type. :return: SoftLayer_Notification_Occurrence_Event """ - mask = "mask[id, subject, startDate, endDate, statusCode, acknowledgedFlag, impactedResourceCount, updateCount]" + mask = "mask[id, subject, startDate, endDate, modifyDate, statusCode, acknowledgedFlag, " \ + "impactedResourceCount, updateCount, systemTicketId, notificationOccurrenceEventType[keyName]]" + _filter = { - 'endDate': { - 'operation': '> sysdate' - }, - 'startDate': { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + + self.add_event_filter(_filter, event_type) + + return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + @staticmethod + def add_event_filter(_filter, event_type): + """Add data to the object filter. + + :param: _filter: event filter. + :param: string event_type: event type. + """ + if event_type == 'PLANNED': + _filter['endDate'] = { + 'operation': '> sysdate - 2' + } + _filter['startDate'] = { 'operation': 'orderBy', 'options': [{ 'name': 'sort', - 'value': ['ASC'] + 'value': ['DESC'] }] } - } - return self.client.call('Notification_Occurrence_Event', 'getAllObjects', filter=_filter, mask=mask, iter=True) + + if event_type == 'UNPLANNED_INCIDENT': + _filter['modifyDate'] = { + 'operation': '> sysdate - 2' + } + + if event_type == 'ANNOUNCEMENT': + _filter['statusCode'] = { + 'keyName': { + 'operation': 'in', + 'options': [{ + 'name': 'data', + 'value': ['PUBLISHED'] + }] + } + } def ack_event(self, event_id): """Acknowledge an event. This mostly prevents it from appearing as a notification in the control portal. diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 513aa44ff..b47ec6abb 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -18,10 +18,51 @@ def test_get_summary(self): self.manager.get_summary() self.assert_called_with('SoftLayer_Account', 'getObject') - def test_get_upcoming_events(self): - self.manager.get_upcoming_events() + def test_get_planned_upcoming_events(self): + self.manager.get_upcoming_events("PLANNED") self.assert_called_with(self.SLNOE, 'getAllObjects') + def test_get_unplanned_upcoming_events(self): + self.manager.get_upcoming_events("UNPLANNED_INCIDENT") + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_get_announcement_upcoming_events(self): + self.manager.get_upcoming_events("ANNOUNCEMENT") + self.assert_called_with(self.SLNOE, 'getAllObjects') + + def test_add_planned_event_filter(self): + event_type = 'PLANNED' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + + def test_add_unplanned_event_filter(self): + event_type = 'UNPLANNED_INCIDENT' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + + def test_add_announcement_event_filter(self): + event_type = 'ANNOUNCEMENT' + _filter = { + 'notificationOccurrenceEventType': { + 'keyName': { + 'operation': event_type + } + } + } + self.manager.add_event_filter(_filter, event_type) + def test_ack_event(self): self.manager.ack_event(12345) self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=12345) From 975435d0fcafe2cdc77acb71138132f82490357e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 12:50:54 -0400 Subject: [PATCH 0330/1582] fix analysis tox. --- SoftLayer/CLI/account/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index c80614969..689b5edad 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -2,10 +2,10 @@ # :license: MIT, see LICENSE for more details. import click -from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import utils @click.command() From 7203df063f2927fcce42d154ba278d57a8dae847 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 28 Apr 2020 15:01:15 -0400 Subject: [PATCH 0331/1582] fix analysis tox method docstring. --- SoftLayer/CLI/account/events.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index 689b5edad..b5d8960cb 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -31,6 +31,7 @@ def cli(env, ack_all): def add_ack_flag(events, manager, ack_all): + """Add acknowledgedFlag to the event""" if ack_all: for event in events: result = manager.ack_event(event['id']) From 47574a40f1a8bb47bde475e7dea21de5deab27b2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 28 Apr 2020 16:23:44 -0500 Subject: [PATCH 0332/1582] fixing pylint 2.5.0 new errors --- SoftLayer/managers/dedicated_host.py | 15 ++++++----- SoftLayer/managers/hardware.py | 40 ++++++++++++---------------- SoftLayer/managers/metadata.py | 5 ++-- SoftLayer/managers/vs_capacity.py | 5 ++-- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 86258416b..24cb3f300 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -7,8 +7,9 @@ """ import logging -import SoftLayer +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer import utils @@ -395,7 +396,7 @@ def _get_location(self, regions, datacenter): if region['location']['location']['name'] == datacenter: return region - raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'" % datacenter) + raise SoftLayerError("Could not find valid location for: '%s'" % datacenter) def get_create_options(self): """Returns valid options for ordering a dedicated host.""" @@ -426,7 +427,7 @@ def _get_price(self, package): if not price.get('locationGroupId'): return price['id'] - raise SoftLayer.SoftLayerError("Could not find valid price") + raise SoftLayerError("Could not find valid price") def _get_item(self, package, flavor): """Returns the item for ordering a dedicated host.""" @@ -435,7 +436,7 @@ def _get_item(self, package, flavor): if item['keyName'] == flavor: return item - raise SoftLayer.SoftLayerError("Could not find valid item for: '%s'" % flavor) + raise SoftLayerError("Could not find valid item for: '%s'" % flavor) def _get_backend_router(self, locations, item): """Returns valid router options for ordering a dedicated host.""" @@ -495,7 +496,7 @@ def _get_backend_router(self, locations, item): routers = self.host.getAvailableRouters(host, mask=mask) return routers - raise SoftLayer.SoftLayerError("Could not find available routers") + raise SoftLayerError("Could not find available routers") def _get_default_router(self, routers, router_name=None): """Returns the default router for ordering a dedicated host.""" @@ -508,7 +509,7 @@ def _get_default_router(self, routers, router_name=None): if router['hostname'] == router_name: return router['id'] - raise SoftLayer.SoftLayerError("Could not find valid default router") + raise SoftLayerError("Could not find valid default router") def get_router_options(self, datacenter=None, flavor=None): """Returns available backend routers for the dedicated host.""" @@ -524,7 +525,7 @@ def _delete_guest(self, guest_id): msg = 'Cancelled' try: self.guest.deleteObject(id=guest_id) - except SoftLayer.SoftLayerAPIError as e: + except SoftLayerAPIError as e: msg = 'Exception: ' + e.faultString return msg diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 99ab45b9c..9d7cbf3f8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,9 +9,10 @@ import socket import time -import SoftLayer from SoftLayer.decoration import retry +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering +from SoftLayer.managers.ticket import TicketManager from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -77,19 +78,18 @@ def cancel_hardware(self, hardware_id, reason='unneeded', comment='', immediate= # Get cancel reason reasons = self.get_cancellation_reasons() cancel_reason = reasons.get(reason, reasons['unneeded']) - ticket_mgr = SoftLayer.TicketManager(self.client) + ticket_mgr = TicketManager(self.client) mask = 'mask[id, hourlyBillingFlag, billingItem[id], openCancellationTicket[id], activeTransaction]' hw_billing = self.get_hardware(hardware_id, mask=mask) if 'activeTransaction' in hw_billing: - raise SoftLayer.SoftLayerError("Unable to cancel hardware with running transaction") + raise SoftLayerError("Unable to cancel hardware with running transaction") if 'billingItem' not in hw_billing: if utils.lookup(hw_billing, 'openCancellationTicket', 'id'): - raise SoftLayer.SoftLayerError("Ticket #%s already exists for this server" % - hw_billing['openCancellationTicket']['id']) - raise SoftLayer.SoftLayerError("Cannot locate billing for the server. " - "The server may already be cancelled.") + raise SoftLayerError("Ticket #%s already exists for this server" % + hw_billing['openCancellationTicket']['id']) + raise SoftLayerError("Cannot locate billing for the server. The server may already be cancelled.") billing_id = hw_billing['billingItem']['id'] @@ -744,7 +744,7 @@ def _get_extra_price_id(items, key_name, hourly, location): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for extra option, '%s'" % key_name) @@ -762,7 +762,7 @@ def _get_default_price_id(items, option, hourly, location): _matches_location(price, location)]): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for '%s' option" % option) @@ -792,7 +792,7 @@ def _get_bandwidth_price_id(items, return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for bandwidth option") @@ -800,11 +800,8 @@ def _get_os_price_id(items, os, location): """Returns the price id matching.""" for item in items: - if any([utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'os', - utils.lookup(item, - 'keyName') != os]): + if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'os', + utils.lookup(item, 'keyName') != os]): continue for price in item['prices']: @@ -813,17 +810,14 @@ def _get_os_price_id(items, os, location): return price['id'] - raise SoftLayer.SoftLayerError("Could not find valid price for os: '%s'" % - os) + raise SoftLayerError("Could not find valid price for os: '%s'" % os) def _get_port_speed_price_id(items, port_speed, no_public, location): """Choose a valid price id for port speed.""" for item in items: - if utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'port_speed': + if utils.lookup(item, 'itemCategory', 'categoryCode') != 'port_speed': continue # Check for correct capacity and if the item matches private only @@ -838,7 +832,7 @@ def _get_port_speed_price_id(items, port_speed, no_public, location): return price['id'] - raise SoftLayer.SoftLayerError( + raise SoftLayerError( "Could not find valid price for port speed: '%s'" % port_speed) @@ -887,7 +881,7 @@ def _get_location(package, location): if region['location']['location']['name'] == location: return region - raise SoftLayer.SoftLayerError("Could not find valid location for: '%s'" % location) + raise SoftLayerError("Could not find valid location for: '%s'" % location) def _get_preset_id(package, size): @@ -896,4 +890,4 @@ def _get_preset_id(package, size): if preset['keyName'] == size or preset['id'] == size: return preset['id'] - raise SoftLayer.SoftLayerError("Could not find valid size for: '%s'" % size) + raise SoftLayerError("Could not find valid size for: '%s'" % size) diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index cdc021515..603ff23aa 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -5,7 +5,8 @@ :license: MIT, see LICENSE for more details. """ -import SoftLayer +# import SoftLayer +from SoftLayer.API import BaseClient from SoftLayer import consts from SoftLayer import exceptions from SoftLayer import transports @@ -66,7 +67,7 @@ def __init__(self, client=None, timeout=5): timeout=timeout, endpoint_url=consts.API_PRIVATE_ENDPOINT_REST, ) - client = SoftLayer.BaseClient(transport=transport) + client = BaseClient(transport=transport) self.client = client diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index c2be6a615..453d51b7f 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -7,8 +7,9 @@ """ import logging -import SoftLayer +# import SoftLayer +from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.vs import VSManager from SoftLayer import utils @@ -144,7 +145,7 @@ def create_guest(self, capacity_id, test, guest_object): capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName'] flavor = _flavor_string(capacity_flavor, guest_object['primary_disk']) except KeyError: - raise SoftLayer.SoftLayerError("Unable to find capacity Flavor.") + raise SoftLayerError("Unable to find capacity Flavor.") guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] From 2d6155aa646dc991ec8a3d7b55718677d739cf71 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 28 Apr 2020 16:25:11 -0500 Subject: [PATCH 0333/1582] removing dead imports --- SoftLayer/managers/dedicated_host.py | 1 - SoftLayer/managers/metadata.py | 1 - SoftLayer/managers/vs_capacity.py | 1 - 3 files changed, 3 deletions(-) diff --git a/SoftLayer/managers/dedicated_host.py b/SoftLayer/managers/dedicated_host.py index 24cb3f300..89246da28 100644 --- a/SoftLayer/managers/dedicated_host.py +++ b/SoftLayer/managers/dedicated_host.py @@ -5,7 +5,6 @@ :license: MIT, see License for more details. """ - import logging from SoftLayer.exceptions import SoftLayerAPIError diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index 603ff23aa..13f350add 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -5,7 +5,6 @@ :license: MIT, see LICENSE for more details. """ -# import SoftLayer from SoftLayer.API import BaseClient from SoftLayer import consts from SoftLayer import exceptions diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 453d51b7f..3f6574f12 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -7,7 +7,6 @@ """ import logging -# import SoftLayer from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering From d078f0eca776915415b443d6e1bdfe68de8e99a3 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 10:57:11 -0400 Subject: [PATCH 0334/1582] fix the Christopehr code review comments --- SoftLayer/CLI/hardware/edit.py | 16 ++++++++-------- SoftLayer/managers/hardware.py | 6 +++--- tests/CLI/modules/server_tests.py | 6 +++--- tests/managers/hardware_tests.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index aad0b68b4..dc1152c6f 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -22,10 +22,10 @@ help="Public port speed. -1 is best speed available") @click.option('--private-speed', default=None, type=click.Choice(['0', '10', '100', '1000', '10000', '-1']), help="Private port speed. -1 is best speed available") -@click.option('--rebundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") +@click.option('--redundant', is_flag=True, default=False, help="The desired state of redundancy for the interface(s)") @click.option('--degraded', is_flag=True, default=False, help="The desired state of degraded for the interface(s)") @environment.pass_env -def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, rebundant, degraded): +def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed, private_speed, redundant, degraded): """Edit hardware details.""" if userdata and userfile: @@ -53,17 +53,17 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed raise exceptions.CLIAbort("Failed to update hardware") if public_speed is not None: - if rebundant: - mgr.change_port_speed(hw_id, True, int(public_speed), 'rebundant') + if redundant: + mgr.change_port_speed(hw_id, True, int(public_speed), 'redundant') if degraded: mgr.change_port_speed(hw_id, True, int(public_speed), 'degraded') - if not rebundant and not degraded: + if not redundant and not degraded: raise exceptions.CLIAbort("Failed to update hardwar") if private_speed is not None: - if rebundant: - mgr.change_port_speed(hw_id, False, int(private_speed), 'rebundant') + if redundant: + mgr.change_port_speed(hw_id, False, int(private_speed), 'redundant') if degraded: mgr.change_port_speed(hw_id, False, int(private_speed), 'degraded') - if not rebundant and not degraded: + if not redundant and not degraded: raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e46674bf2..1934fa0eb 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -297,7 +297,7 @@ def rescue(self, hardware_id): """ return self.hardware.bootToRescueLayer(id=hardware_id) - def change_port_speed(self, hardware_id, public, speed, rebundant): + def change_port_speed(self, hardware_id, public, speed, redundant=None): """Allows you to change the port speed of a server's NICs. :param int hardware_id: The ID of the server @@ -319,11 +319,11 @@ def change_port_speed(self, hardware_id, public, speed, rebundant): if public: return self.client.call('Hardware_Server', 'setPublicNetworkInterfaceSpeed', - [rebundant, speed], id=hardware_id) + [speed, redundant], id=hardware_id) else: return self.client.call('Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - [rebundant, speed], id=hardware_id) + [speed, redundant], id=hardware_id) def place_order(self, **kwargs): """Places an order for a piece of hardware. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 25cb20de4..157acfcc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -526,7 +526,7 @@ def test_edit(self): '--tag=dev', '--tag=green', '--public-speed=10', - '--rebundant', + '--redundant', '--private-speed=100', '--degraded', '100']) @@ -546,12 +546,12 @@ def test_edit(self): ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', - args=(['rebundant', 10],), + args=([10, 'redundant'],), identifier=100, ) self.assert_called_with( 'SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', - args=(['degraded', 100],), + args=([100, 'degraded'],), identifier=100, ) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 472e25c75..06b2a334f 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -343,12 +343,12 @@ def test_change_port_speed_public(self): args=(['degraded', 100],)) def test_change_port_speed_private(self): - self.hardware.change_port_speed(2, False, 10, 'rebundant') + self.hardware.change_port_speed(2, False, 10, 'redundant') self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=(['rebundant', 10],)) + args=([10,'redundant'],)) def test_edit_meta(self): # Test editing user data From 89e873b530815425b2079880efe6022c693df474 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 11:05:34 -0400 Subject: [PATCH 0335/1582] fix tox tool --- tests/managers/hardware_tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 06b2a334f..f504dba94 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -340,7 +340,7 @@ def test_change_port_speed_public(self): self.assert_called_with('SoftLayer_Hardware_Server', 'setPublicNetworkInterfaceSpeed', identifier=2, - args=(['degraded', 100],)) + args=([100, 'degraded'],)) def test_change_port_speed_private(self): self.hardware.change_port_speed(2, False, 10, 'redundant') @@ -348,7 +348,7 @@ def test_change_port_speed_private(self): self.assert_called_with('SoftLayer_Hardware_Server', 'setPrivateNetworkInterfaceSpeed', identifier=2, - args=([10,'redundant'],)) + args=([10, 'redundant'],)) def test_edit_meta(self): # Test editing user data @@ -374,10 +374,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From 5462fd076377e74f85c3aa502f1de77bad86cc20 Mon Sep 17 00:00:00 2001 From: Daniel Cabero Barrios Date: Wed, 29 Apr 2020 18:30:46 -0400 Subject: [PATCH 0336/1582] fix the isue 1262 --- SoftLayer/managers/ordering.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 7e4e81db5..b5e659ee1 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -251,14 +251,13 @@ def list_categories(self, package_keyname, **kwargs): :param str package_keyname: The package for which to get the categories. :returns: List of categories associated with the package """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', CATEGORY_MASK) + kwargs['mask'] = kwargs.get('mask', CATEGORY_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] package = self.get_package_by_key(package_keyname, mask='id') - categories = self.package_svc.getConfiguration(id=package['id'], **get_kwargs) + categories = self.package_svc.getConfiguration(id=package['id'], **kwargs) return categories def list_items(self, package_keyname, **kwargs): @@ -268,14 +267,11 @@ def list_items(self, package_keyname, **kwargs): :returns: List of items in the package """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', ITEM_MASK) - - if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + if 'mask' not in kwargs: + kwargs['mask'] = ITEM_MASK package = self.get_package_by_key(package_keyname, mask='id') - items = self.package_svc.getItems(id=package['id'], **get_kwargs) + items = self.package_svc.getItems(id=package['id'], **kwargs) return items def list_packages(self, **kwargs): @@ -284,13 +280,12 @@ def list_packages(self, **kwargs): :returns: List of active packages. """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', PACKAGE_MASK) + kwargs['mask'] = kwargs.get('mask', PACKAGE_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] - packages = self.package_svc.getAllObjects(**get_kwargs) + packages = self.package_svc.getAllObjects(**kwargs) return [package for package in packages if package['isActive']] @@ -301,15 +296,15 @@ def list_presets(self, package_keyname, **kwargs): :returns: A list of package presets that can be used for ordering """ - get_kwargs = {} - get_kwargs['mask'] = kwargs.get('mask', PRESET_MASK) + + kwargs['mask'] = kwargs.get('mask', PRESET_MASK) if 'filter' in kwargs: - get_kwargs['filter'] = kwargs['filter'] + kwargs['filter'] = kwargs['filter'] package = self.get_package_by_key(package_keyname, mask='id') - acc_presets = self.package_svc.getAccountRestrictedActivePresets(id=package['id'], **get_kwargs) - active_presets = self.package_svc.getActivePresets(id=package['id'], **get_kwargs) + acc_presets = self.package_svc.getAccountRestrictedActivePresets(id=package['id'], **kwargs) + active_presets = self.package_svc.getActivePresets(id=package['id'], **kwargs) return active_presets + acc_presets def get_preset_by_key(self, package_keyname, preset_keyname, mask=None): From 998572c72c3e8db94a69e04f8751cd902390ba53 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:32:20 -0500 Subject: [PATCH 0337/1582] #1266 added JSON encoder for bytes objects --- SoftLayer/transports.py | 12 +++++++++++- tests/transport_tests.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 297249852..afa8df88f 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import base64 import importlib import json import logging @@ -359,7 +360,7 @@ def __call__(self, request): body['parameters'] = request.args if body: - request.payload = json.dumps(body) + request.payload = json.dumps(body, cls=ComplexEncoder) url_parts = [self.endpoint_url, request.service] if request.identifier is not None: @@ -566,3 +567,12 @@ def _format_object_mask(objectmask): not objectmask.startswith('[')): objectmask = "mask[%s]" % objectmask return objectmask + +class ComplexEncoder(json.JSONEncoder): + def default(self, obj): + # Base64 encode bytes type objects. + if isinstance(obj, bytes): + base64_bytes = base64.b64encode(obj) + return base64_bytes.decode("utf-8") + # Let the base class default method raise the TypeError + return json.JSONEncoder.default(self, obj) \ No newline at end of file diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d105c3fdc..d8e8245c5 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -7,6 +7,7 @@ import io import warnings +import json import mock import pytest import requests @@ -527,6 +528,31 @@ def test_with_args(self, request): 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://something.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 = '{}' @@ -674,6 +700,14 @@ def test_print_reproduceable(self): 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) + self.assertEqual(result, '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}') + class TestFixtureTransport(testing.TestCase): From 8714c03edb39c70e71062d66752d7ebc534ab3d6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:42:13 -0500 Subject: [PATCH 0338/1582] tox fixes --- SoftLayer/transports.py | 13 +++++++++---- tests/transport_tests.py | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index afa8df88f..02cd7a214 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -568,11 +568,16 @@ def _format_object_mask(objectmask): objectmask = "mask[%s]" % objectmask return objectmask + class ComplexEncoder(json.JSONEncoder): - def default(self, obj): + """ComplexEncoder helps jsonencoder deal with byte strings""" + + def default(self, o): + """Encodes o as JSON""" + # Base64 encode bytes type objects. - if isinstance(obj, bytes): - base64_bytes = base64.b64encode(obj) + 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, obj) \ No newline at end of file + return json.JSONEncoder.default(self, o) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index d8e8245c5..8267c6f58 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -528,7 +528,6 @@ def test_with_args(self, request): proxies=None, timeout=None) - @mock.patch('SoftLayer.transports.requests.Session.request') def test_with_args_bytes(self, request): request().text = '{}' @@ -702,7 +701,7 @@ def test_print_reproduceable(self): def test_complex_encoder_bytes(self): to_encode = { - 'test' : ['array', 0, 1, False], + 'test': ['array', 0, 1, False], 'bytes': b'ASDASDASD' } result = json.dumps(to_encode, cls=transports.ComplexEncoder) From 99879fccbb7f94b71434e9365cc4891e3c3624d7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 30 Apr 2020 16:54:38 -0500 Subject: [PATCH 0339/1582] fixed unit test issues --- tests/transport_tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 8267c6f58..6e6c793fd 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -705,7 +705,9 @@ def test_complex_encoder_bytes(self): 'bytes': b'ASDASDASD' } result = json.dumps(to_encode, cls=transports.ComplexEncoder) - self.assertEqual(result, '{"test": ["array", 0, 1, false], "bytes": "QVNEQVNEQVNE"}') + # 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): From 5db394e4c164ed6a50adc910cfc7ed0943570e98 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 18 May 2020 07:18:32 -0500 Subject: [PATCH 0340/1582] v5.8.8 release --- CHANGELOG.md | 17 +++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 965e9967f..626c66af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Change Log + +## [5.8.8] - 2020-05-18 +https://github.com/softlayer/softlayer-python/compare/v5.8.7...v5.8.8 + +- #1266 Fixed ticket upload with REST endpoint +- #1263 add the redundant/degraded option to hardware +- #1262 Added `iter` option for ordering manager functions +- #1264 Add Account planned, unplanned and announcement events +- #1265 fixed pylint 2.5.0 errors +- #1261 Fix AttributeError: 'NoneType' object has no attribute 'keys +- #1256 Adding more github action tests, removing travis CI tests +- #887 fix Response shows additional new lines (\n) in ticket details +- #1241 Storage feature for virtual and hardware servers +- #1242 Hardware and Virtual billing info +- #1239 VPN subnet access to a use +- #1254 added account billing-items/item-details/cancel-item commands + ## [5.8.7] - 2020-03-26 https://github.com/softlayer/softlayer-python/compare/v5.8.5...v5.8.7 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index e651d91ca..0ea903bf5 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.7' +VERSION = 'v5.8.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 d8e9f566f..6a6c9a551 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.7', + version='5.8.8', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From bd7cc6d3afea03fe11a468b8fdf9c971ab21e052 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:18:31 -0500 Subject: [PATCH 0341/1582] #1252 added automated snap publisher --- .github/workflows/release.yml | 23 +++++++++++++++++++++++ snap/snapcraft.yaml | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..397168add --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-latest + strategy: + matrix: + arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] + steps: + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.1.1 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable + diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b08438c82..474b05f1b 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: slcli # check to see if it's available -version: '5.8.1+git' # check versioning +version: 'git' # will be replaced by a `git describe` based version string summary: Python based SoftLayer API Tool. # 79 char long summary description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From b81c031b7a874fc9e9e796cffb6e2c5d2c2b8573 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:30:06 -0500 Subject: [PATCH 0342/1582] fixed typo in release github action --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 397168add..f16f7934c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] - steps: + steps: - name: Install Snapcraft uses: samuelmeuli/action-snapcraft@v1.1.1 with: From 81808b6f760ed52bc54b6e09ac13d2c7d7d7d1a4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 26 May 2020 14:37:18 -0500 Subject: [PATCH 0343/1582] Update release.yml --- .github/workflows/release.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f16f7934c..36ef10414 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,13 +11,13 @@ jobs: matrix: arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] steps: - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1.1.1 - with: - snapcraft_token: ${{ secrets.snapcraft_token }} - - name: Push to stable - run: | - VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` - echo Publishing $VERSION on ${{ matrix.arch }} - snapcraft release slcli $VERSION stable + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.1.1 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable From 078f9f1aa623ed2f2d0d36f9dcccd062dc8e05a6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 1 Jun 2020 17:03:44 -0500 Subject: [PATCH 0344/1582] #1230 basic tag listing --- SoftLayer/CLI/core.py | 1 + SoftLayer/CLI/routes.py | 3 ++ SoftLayer/CLI/tags/__init__.py | 1 + SoftLayer/CLI/tags/list.py | 75 +++++++++++++++++++++++++++++ SoftLayer/managers/__init__.py | 2 + SoftLayer/managers/tags.py | 87 ++++++++++++++++++++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 SoftLayer/CLI/tags/__init__.py create mode 100644 SoftLayer/CLI/tags/list.py create mode 100644 SoftLayer/managers/tags.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index fe86f714e..362ff72e7 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -35,6 +35,7 @@ PROG_NAME = "slcli (SoftLayer Command-line)" VALID_FORMATS = ['table', 'raw', 'json', 'jsonraw'] DEFAULT_FORMAT = 'raw' + if sys.stdout.isatty(): DEFAULT_FORMAT = 'table' diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 452ee0f9c..6c2bae945 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -290,6 +290,9 @@ ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), + ('tags', 'SoftLayer.CLI.tags'), + ('tags:list', 'SoftLayer.CLI.tags.list:cli'), + ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), ('ticket:detail', 'SoftLayer.CLI.ticket.detail:cli'), diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py new file mode 100644 index 000000000..e5caefa78 --- /dev/null +++ b/SoftLayer/CLI/tags/__init__.py @@ -0,0 +1 @@ +"""Manage Tags""" \ No newline at end of file diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py new file mode 100644 index 000000000..71634478a --- /dev/null +++ b/SoftLayer/CLI/tags/list.py @@ -0,0 +1,75 @@ +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + +from pprint import pprint as pp + +@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) + for table in tables: + env.fout(table) + else: + table = simple_table(tag_manager) + env.fout(table) + # pp(tags.list_tags()) + + +def tag_row(tag): + return [tag.get('id'), tag.get('name'), tag.get('referenceCount',0)] + +def detailed_table(tag_manager): + """Creates a table for each tag, with details about resources using it""" + tags = tag_manager.get_attached_tags() + 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""" + try: + resource = tag_manager.reference_lookup(resource_id, tag_type) + if tag_type == 'NETWORK_VLAN_FIREWALL': + resource_row = resource.get('primaryIpAddress') + else: + resource_row = resource.get('fullyQualifiedDomainName') + except SoftLayerAPIError as e: + resource_row = "{}".format(e.reason) + return resource_row \ No newline at end of file diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 5c489345d..8053ec70e 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -24,6 +24,7 @@ from SoftLayer.managers.ordering import OrderingManager from SoftLayer.managers.sshkey import SshKeyManager from SoftLayer.managers.ssl import SSLManager +from SoftLayer.managers.tags import TagManager from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.user import UserManager from SoftLayer.managers.vs import VSManager @@ -50,6 +51,7 @@ 'PlacementManager', 'SshKeyManager', 'SSLManager', + 'TagManager', 'TicketManager', 'UserManager', 'VSManager', diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py new file mode 100644 index 000000000..8e6d282bd --- /dev/null +++ b/SoftLayer/managers/tags.py @@ -0,0 +1,87 @@ +""" + 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} + # return [unattached, attached] + + def get_unattached_tags(self, mask=None): + """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser()""" + return self.client.call('SoftLayer_Tag', 'getUnattachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_attached_tags(self, mask=None): + """Calls SoftLayer_Tag::getAttachedTagsForCurrentUser()""" + return self.client.call('SoftLayer_Tag', 'getAttachedTagsForCurrentUser', + mask=mask, iter=True) + + def get_tag_references(self, tag_id, mask=None): + if mask is None: + mask="mask[tagType]" + return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) + + 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| + """ + + if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: + raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + + 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' + 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 {} + return self.client.call(service, 'getObject', id=resource_table_id) + + + From 54853e2faf31f6bbf64ef1e584f5673c6e507784 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 09:28:40 -0500 Subject: [PATCH 0345/1582] added tag docs --- docs/cli/tags.rst | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docs/cli/tags.rst diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst new file mode 100644 index 000000000..5cca010f8 --- /dev/null +++ b/docs/cli/tags.rst @@ -0,0 +1,9 @@ +.. _cli_tags: + +Tag Commands +============ + + +.. click:: SoftLayer.CLI.tags.list:cli + :prog: tags list + :show-nested: From 9cc1c4ee0bc31e04f8c7f491a4dc0cda115161e8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:16:30 -0500 Subject: [PATCH 0346/1582] #1230 added unit tests --- SoftLayer/fixtures/SoftLayer_Hardware.py | 71 ++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 16 ++++ SoftLayer/managers/tags.py | 16 +++- tests/CLI/modules/tag_tests.py | 27 ++++++ tests/managers/tag_tests.py | 116 +++++++++++++++++++++++ 5 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Hardware.py create mode 100644 SoftLayer/fixtures/SoftLayer_Tag.py create mode 100644 tests/CLI/modules/tag_tests.py create mode 100644 tests/managers/tag_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py new file mode 100644 index 000000000..edaa0554c --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -0,0 +1,71 @@ +getObject = { + 'id': 1000, + 'globalIdentifier': '1a2b3c-1701', + 'datacenter': {'id': 50, 'name': 'TEST00', + 'description': 'Test Data Center'}, + 'billingItem': { + 'id': 6327, + 'recurringFee': 1.54, + 'nextInvoiceTotalRecurringAmount': 16.08, + 'children': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, + ], + 'orderItem': { + 'order': { + 'userRecord': { + 'username': 'chechu', + } + } + } + }, + 'primaryIpAddress': '172.16.1.100', + 'hostname': 'hardware-test1', + 'domain': 'test.sftlyr.ws', + 'bareMetalInstanceFlag': True, + 'fullyQualifiedDomainName': 'hardware-test1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 2, + 'memoryCapacity': 2, + 'primaryBackendIpAddress': '10.1.0.2', + 'networkManagementIpAddress': '10.1.0.3', + 'hardwareStatus': {'status': 'ACTIVE'}, + 'primaryNetworkComponent': {'maxSpeed': 10, 'speed': 10}, + 'provisionDate': '2013-08-01 15:23:45', + 'notes': 'These are test notes.', + 'operatingSystem': { + 'softwareLicense': { + 'softwareDescription': { + 'referenceCode': 'UBUNTU_12_64', + 'name': 'Ubuntu', + 'version': 'Ubuntu 12.04 LTS', + } + }, + 'passwords': [ + {'username': 'root', 'password': 'abc123'} + ], + }, + 'remoteManagementAccounts': [ + {'username': 'root', 'password': 'abc123'} + ], + 'networkVlans': [ + { + 'networkSpace': 'PRIVATE', + 'vlanNumber': 1800, + 'id': 9653 + }, + { + 'networkSpace': 'PUBLIC', + 'vlanNumber': 3672, + 'id': 19082 + }, + ], + 'tagReferences': [ + {'tag': {'name': 'test_tag'}} + ], + 'activeTransaction': { + 'transactionStatus': { + 'name': 'TXN_NAME', + 'friendlyName': 'Friendly Transaction Name', + 'id': 6660 + } + } +} \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py new file mode 100644 index 000000000..221efc06b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -0,0 +1,16 @@ +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 +} +] \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 8e6d282bd..234a5e7b6 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -25,19 +25,29 @@ def list_tags(self, mask=None): unattached = self.get_unattached_tags(mask) attached = self.get_attached_tags(mask) return {'attached': attached, 'unattached': unattached} - # return [unattached, attached] def get_unattached_tags(self, mask=None): - """Calls SoftLayer_Tag::getUnattachedTagsForCurrentUser()""" + """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()""" + """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) diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py new file mode 100644 index 000000000..1e578fd3c --- /dev/null +++ b/tests/CLI/modules/tag_tests.py @@ -0,0 +1,27 @@ +""" + SoftLayer.tests.CLI.modules.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account +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) \ No newline at end of file diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py new file mode 100644 index 000000000..ed23111ec --- /dev/null +++ b/tests/managers/tag_tests.py @@ -0,0 +1,116 @@ +""" + SoftLayer.tests.managers.tag_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +import mock +import sys +import unittest + +import SoftLayer +from SoftLayer import fixtures +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 = SoftLayer.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' + + result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) + + def test_reference_lookup_hardware(self): + resource_id = 12345 + tag_type = 'HARDWARE' + + result = 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' + + result = 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' + + result = 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' + + result = 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") \ No newline at end of file From 4840c6b776d119006f332e388e591af2e2720d87 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:25:34 -0500 Subject: [PATCH 0347/1582] tox fixes --- SoftLayer/CLI/tags/list.py | 10 +++++++--- SoftLayer/fixtures/SoftLayer_Tag.py | 26 +++++++++++++------------- SoftLayer/managers/tags.py | 8 +++----- tests/CLI/modules/tag_tests.py | 4 ++-- tests/managers/tag_tests.py | 19 ++++++++++--------- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index 71634478a..a02f0dde1 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -13,6 +13,7 @@ from pprint import pprint as pp + @click.command() @click.option('--detail', '-d', is_flag=True, default=False, help="Show information about the resources using this tag.") @@ -21,7 +22,7 @@ def cli(env, detail): """List Tags.""" tag_manager = TagManager(env.client) - + if detail: tables = detailed_table(tag_manager) for table in tables: @@ -33,7 +34,8 @@ def cli(env, detail): def tag_row(tag): - return [tag.get('id'), tag.get('name'), tag.get('referenceCount',0)] + return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] + def detailed_table(tag_manager): """Creates a table for each tag, with details about resources using it""" @@ -52,6 +54,7 @@ def detailed_table(tag_manager): return tables + def simple_table(tag_manager): """Just tags and how many resources on each""" tags = tag_manager.list_tags() @@ -62,6 +65,7 @@ def simple_table(tag_manager): 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""" try: @@ -72,4 +76,4 @@ def get_resource_name(tag_manager, resource_id, tag_type): resource_row = resource.get('fullyQualifiedDomainName') except SoftLayerAPIError as e: resource_row = "{}".format(e.reason) - return resource_row \ No newline at end of file + return resource_row diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 221efc06b..3246870c0 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -1,16 +1,16 @@ 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 -} -] \ No newline at end of file + { + 'id': 73009305, + 'resourceTableId': 33488921, + 'tag': { + 'id': 1286571, + 'name': 'bs_test_instance', + }, + 'tagId': 1286571, + 'tagType': {'description': 'CCI', 'keyName': 'GUEST'}, + 'tagTypeId': 2, + 'usrRecordId': 6625205 + } +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 234a5e7b6..a2b1a7251 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -9,6 +9,7 @@ from SoftLayer.exceptions import SoftLayerAPIError + class TagManager(object): """Manager for Tag functions.""" @@ -49,7 +50,7 @@ def get_tag_references(self, tag_id, mask=None): :params string mask: Mask to use. """ if mask is None: - mask="mask[tagType]" + mask = "mask[tagType]" return self.client.call('SoftLayer_Tag', 'getReferences', id=tag_id, mask=mask, iter=True) def reference_lookup(self, resource_table_id, tag_type): @@ -77,7 +78,7 @@ def reference_lookup(self, resource_table_id, tag_type): if tag_type in ['ACCOUNT_DOCUMENT', 'CONTRACT']: raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - if tag_type == 'APPLICATION_DELIVERY_CONTROLLER' : + if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': service = 'Network_Application_Delivery_Controller' elif tag_type == 'GUEST': service = 'Virtual_Guest' @@ -92,6 +93,3 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) - - - diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 1e578fd3c..de0d9e07c 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -20,8 +20,8 @@ def test_list(self): 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.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) \ No newline at end of file + self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject', identifier=33488921) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index ed23111ec..899d38a0e 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -69,48 +69,49 @@ def test_get_tag_references_mask(self): def test_reference_lookup_hardware(self): resource_id = 12345 - tag_type = 'HARDWARE' + tag_type = 'HARDWARE' result = self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) def test_reference_lookup_hardware(self): resource_id = 12345 - tag_type = 'HARDWARE' + tag_type = 'HARDWARE' result = 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' + tag_type = 'GUEST' result = 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' + tag_type = 'APPLICATION_DELIVERY_CONTROLLER' result = self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=resource_id) + 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' + tag_type = 'DEDICATED_HOST' result = 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' + tag_type = 'ACCOUNT_DOCUMENT' exception = self.assertRaises( SoftLayerAPIError, self.tag_manager.reference_lookup, - resource_id, + resource_id, tag_type ) self.assertEqual(exception.faultCode, 404) - self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") \ No newline at end of file + self.assertEqual(exception.reason, "Unable to lookup ACCOUNT_DOCUMENT types") From a331898b6023e2590edb27e051574b523024b1f5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 2 Jun 2020 11:40:58 -0500 Subject: [PATCH 0348/1582] #1230 fixed tox analysis errors --- SoftLayer/CLI/tags/__init__.py | 2 +- SoftLayer/CLI/tags/list.py | 11 ++-- SoftLayer/fixtures/SoftLayer_Hardware.py | 58 ++++++++----------- .../SoftLayer_Network_Storage_Allowed_Host.py | 32 +++++----- SoftLayer/managers/tags.py | 4 +- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/server_tests.py | 36 ++++++------ tests/CLI/modules/tag_tests.py | 1 - tests/CLI/modules/vs/vs_tests.py | 36 ++++++------ tests/managers/hardware_tests.py | 8 +-- tests/managers/tag_tests.py | 22 ++----- tests/managers/user_tests.py | 4 +- 12 files changed, 95 insertions(+), 121 deletions(-) diff --git a/SoftLayer/CLI/tags/__init__.py b/SoftLayer/CLI/tags/__init__.py index e5caefa78..f8dd3783b 100644 --- a/SoftLayer/CLI/tags/__init__.py +++ b/SoftLayer/CLI/tags/__init__.py @@ -1 +1 @@ -"""Manage Tags""" \ No newline at end of file +"""Manage Tags""" diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index a02f0dde1..870d5faf5 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -3,16 +3,14 @@ import click -from SoftLayer.exceptions import SoftLayerAPIError -from SoftLayer.managers.tags import TagManager 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 -from pprint import pprint as pp - @click.command() @click.option('--detail', '-d', is_flag=True, default=False, @@ -34,6 +32,7 @@ def cli(env, detail): def tag_row(tag): + """Format a tag table row""" return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] @@ -74,6 +73,6 @@ def get_resource_name(tag_manager, resource_id, tag_type): resource_row = resource.get('primaryIpAddress') else: resource_row = resource.get('fullyQualifiedDomainName') - except SoftLayerAPIError as e: - resource_row = "{}".format(e.reason) + except SoftLayerAPIError as exception: + resource_row = "{}".format(exception.reason) return resource_row diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index edaa0554c..cb902b556 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -1,8 +1,8 @@ getObject = { - 'id': 1000, - 'globalIdentifier': '1a2b3c-1701', - 'datacenter': {'id': 50, 'name': 'TEST00', - 'description': 'Test Data Center'}, + 'id': 1234, + 'globalIdentifier': 'xxxxc-asd', + 'datacenter': {'id': 12, 'name': 'DALLAS21', + 'description': 'Dallas 21'}, 'billingItem': { 'id': 6327, 'recurringFee': 1.54, @@ -13,59 +13,47 @@ 'orderItem': { 'order': { 'userRecord': { - 'username': 'chechu', + 'username': 'bob', } } } }, - 'primaryIpAddress': '172.16.1.100', - 'hostname': 'hardware-test1', + 'primaryIpAddress': '4.4.4.4', + 'hostname': 'testtest1', 'domain': 'test.sftlyr.ws', 'bareMetalInstanceFlag': True, - 'fullyQualifiedDomainName': 'hardware-test1.test.sftlyr.ws', - 'processorPhysicalCoreAmount': 2, - 'memoryCapacity': 2, - 'primaryBackendIpAddress': '10.1.0.2', - 'networkManagementIpAddress': '10.1.0.3', + 'fullyQualifiedDomainName': 'testtest1.test.sftlyr.ws', + 'processorPhysicalCoreAmount': 4, + 'memoryCapacity': 4, + 'primaryBackendIpAddress': '10.4.4.4', + 'networkManagementIpAddress': '10.4.4.4', 'hardwareStatus': {'status': 'ACTIVE'}, - 'primaryNetworkComponent': {'maxSpeed': 10, 'speed': 10}, - 'provisionDate': '2013-08-01 15:23:45', - 'notes': 'These are test notes.', + 'primaryNetworkComponent': {'maxSpeed': 1000, 'speed': 1000}, + 'provisionDate': '2020-08-01 15:23:45', + 'notes': 'NOTES NOTES NOTES', 'operatingSystem': { 'softwareLicense': { 'softwareDescription': { - 'referenceCode': 'UBUNTU_12_64', + 'referenceCode': 'UBUNTU_20_64', 'name': 'Ubuntu', - 'version': 'Ubuntu 12.04 LTS', + 'version': 'Ubuntu 20.04 LTS', } }, 'passwords': [ - {'username': 'root', 'password': 'abc123'} + {'username': 'root', 'password': 'xxxxxxxxxxxx'} ], }, 'remoteManagementAccounts': [ - {'username': 'root', 'password': 'abc123'} + {'username': 'root', 'password': 'zzzzzzzzzzzzzz'} ], 'networkVlans': [ { 'networkSpace': 'PRIVATE', - 'vlanNumber': 1800, - 'id': 9653 - }, - { - 'networkSpace': 'PUBLIC', - 'vlanNumber': 3672, - 'id': 19082 + 'vlanNumber': 1234, + 'id': 11111 }, ], 'tagReferences': [ - {'tag': {'name': 'test_tag'}} + {'tag': {'name': 'a tag'}} ], - 'activeTransaction': { - 'transactionStatus': { - 'name': 'TXN_NAME', - 'friendlyName': 'Friendly Transaction Name', - 'id': 6660 - } - } -} \ No newline at end of file +} diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py index 5bf8c3354..923147a58 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Allowed_Host.py @@ -32,22 +32,22 @@ getObject = TEST_ALLOWED_HOST getSubnetsInAcl = [{ - 'id': 12345678, - 'accountId': 1234, - 'networkIdentifier': '10.11.12.13', - 'cidr': '14', - 'billingRecordId': None, - 'parentId': None, - 'networkVlanId': None, - 'createDate': '2020-01-02 00:00:01', - 'modifyDate': None, - 'subnetType': 'SECONDARY_ON_VLAN', - 'restrictAllocationFlag': 0, - 'leafFlag': 1, - 'ownerId': 1, - 'ipAddressBegin': 129123, - 'ipAddressEnd': 129145, - 'purgeFlag': 0 + 'id': 12345678, + 'accountId': 1234, + 'networkIdentifier': '10.11.12.13', + 'cidr': '14', + 'billingRecordId': None, + 'parentId': None, + 'networkVlanId': None, + 'createDate': '2020-01-02 00:00:01', + 'modifyDate': None, + 'subnetType': 'SECONDARY_ON_VLAN', + 'restrictAllocationFlag': 0, + 'leafFlag': 1, + 'ownerId': 1, + 'ipAddressBegin': 129123, + 'ipAddressEnd': 129145, + 'purgeFlag': 0 }] assignSubnetsToAcl = [ diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index a2b1a7251..e22e94654 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -61,8 +61,8 @@ def reference_lookup(self, resource_table_id, tag_type): From SoftLayer_Tag::getAllTagTypes() - |Type |Service | - | ----------------------------- | ------ | + |Type |Service | + | ----------------------------- | ------ | |Hardware |HARDWARE| |CCI |GUEST| |Account Document |ACCOUNT_DOCUMENT| diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index b39face10..e5f6ba8c5 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -112,7 +112,7 @@ def test_volume_detail_name_identifier(self): 'keyName': {'operation': '*= BLOCK_STORAGE'} }, 'username': {'operation': '_= SL-12345'} - } + } } self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage', filter=expected_filter) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 157acfcc1..a7fd4e908 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -694,19 +694,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']) @@ -749,12 +749,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/tag_tests.py b/tests/CLI/modules/tag_tests.py index de0d9e07c..357364063 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,7 +4,6 @@ Tests for the user cli command """ -from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index c61883385..a4bf28509 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -370,19 +370,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']) @@ -425,12 +425,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 f504dba94..69e7ae52a 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -374,10 +374,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 899d38a0e..38f198ae9 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -4,12 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock -import sys -import unittest -import SoftLayer -from SoftLayer import fixtures from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer.managers import tags from SoftLayer import testing @@ -18,7 +13,7 @@ class TagTests(testing.TestCase): def set_up(self): - self.tag_manager = SoftLayer.TagManager(self.client) + self.tag_manager = tags.TagManager(self.client) self.test_mask = "mask[id]" def test_list_tags(self): @@ -71,28 +66,21 @@ def test_reference_lookup_hardware(self): resource_id = 12345 tag_type = 'HARDWARE' - result = self.tag_manager.reference_lookup(resource_id, tag_type) - self.assert_called_with('SoftLayer_Hardware', 'getObject', identifier=resource_id) - - def test_reference_lookup_hardware(self): - resource_id = 12345 - tag_type = 'HARDWARE' - - result = self.tag_manager.reference_lookup(resource_id, tag_type) + 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' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + 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' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + self.tag_manager.reference_lookup(resource_id, tag_type) self.assert_called_with('SoftLayer_Network_Application_Delivery_Controller', 'getObject', identifier=resource_id) @@ -100,7 +88,7 @@ def test_reference_lookup_dedicated(self): resource_id = 12345 tag_type = 'DEDICATED_HOST' - result = self.tag_manager.reference_lookup(resource_id, tag_type) + 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): diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index b75a5a772..b0ab015f9 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -231,7 +231,7 @@ def test_vpn_subnet_add(self): subnet_id = 1234 expected_args = ( [{"userId": user_id, "subnetId": subnet_id}], - ) + ) self.manager.vpn_subnet_add(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'createObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) @@ -242,7 +242,7 @@ def test_vpn_subnet_remove(self): overrides = [{'id': 3661234, 'subnetId': subnet_id}] expected_args = ( overrides, - ) + ) self.manager.vpn_subnet_remove(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) From 2bc9fa0420fc21b73affbf881b955df6232b8020 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 3 Jun 2020 15:44:47 -0400 Subject: [PATCH 0349/1582] Set tags. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/set.py | 24 ++++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 2 ++ SoftLayer/managers/tags.py | 9 +++++++++ tests/CLI/modules/tag_tests.py | 20 ++++++++++++++++++++ tests/managers/tag_tests.py | 8 ++++++++ 6 files changed, 64 insertions(+) create mode 100644 SoftLayer/CLI/tags/set.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6c2bae945..9304e2b33 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -292,6 +292,7 @@ ('tags', 'SoftLayer.CLI.tags'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), + ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py new file mode 100644 index 000000000..aa0879c6e --- /dev/null +++ b/SoftLayer/CLI/tags/set.py @@ -0,0 +1,24 @@ +"""Set 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('--tags', '-t', type=click.STRING, required=True, help='List of tags e.g. "tag1, tag2"') +@click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE") +@click.option('--resource-id', '-r', type=click.INT, required=True, help="ID of the object being tagged") +@environment.pass_env +def cli(env, tags, key_name, resource_id): + """Set Tags.""" + + tag_manager = TagManager(env.client) + tags = tag_manager.set_tags(tags, key_name, resource_id) + + if tags: + click.secho("Set tags successfully", fg='green') + else: + click.secho("Failed to set tags", fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 3246870c0..ca0d952ef 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -14,3 +14,5 @@ 'usrRecordId': 6625205 } ] + +setTags = True diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index e22e94654..fa19d3d3f 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -93,3 +93,12 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) + + 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) diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 357364063..847511e80 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,6 +4,8 @@ Tests for the user cli command """ +import mock + from SoftLayer import testing @@ -24,3 +26,21 @@ def test_list_detail(self): 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),) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index 38f198ae9..f7ab940bb 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -103,3 +103,11 @@ def test_reference_lookup_document(self): ) 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') From 6bdbf963341da83533cb4ad03a55344bb4613a73 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:21:31 -0400 Subject: [PATCH 0350/1582] add tags details --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/details.py | 24 ++++++++++++++++++++++++ SoftLayer/CLI/tags/list.py | 6 +++--- SoftLayer/managers/tags.py | 22 ++++++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/CLI/tags/details.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 9304e2b33..a71b62439 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -293,6 +293,7 @@ ('tags', 'SoftLayer.CLI.tags'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), + ('tags:details', 'SoftLayer.CLI.tags.details:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py new file mode 100644 index 000000000..eb22bfada --- /dev/null +++ b/SoftLayer/CLI/tags/details.py @@ -0,0 +1,24 @@ +"""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') +@environment.pass_env +def cli(env, identifier): + """Get details for a Tag.""" + + tag_manager = TagManager(env.client) + + if str.isdigit(identifier): + 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 870d5faf5..e2f136581 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -22,7 +22,7 @@ def cli(env, detail): tag_manager = TagManager(env.client) if detail: - tables = detailed_table(tag_manager) + tables = detailed_table(tag_manager, tag_manager.get_attached_tags()) for table in tables: env.fout(table) else: @@ -36,9 +36,8 @@ def tag_row(tag): return [tag.get('id'), tag.get('name'), tag.get('referenceCount', 0)] -def detailed_table(tag_manager): +def detailed_table(tag_manager, tags): """Creates a table for each tag, with details about resources using it""" - tags = tag_manager.get_attached_tags() tables = [] for tag in tags: references = tag_manager.get_tag_references(tag.get('id')) @@ -76,3 +75,4 @@ def get_resource_name(tag_manager, resource_id, tag_type): except SoftLayerAPIError as exception: resource_row = "{}".format(exception.reason) return resource_row + diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index fa19d3d3f..e5b43a60f 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -53,6 +53,28 @@ def get_tag_references(self, tag_id, mask=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 From 6be47b817f40dfa297dfb91840d784b226d91bd3 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:22:47 -0400 Subject: [PATCH 0351/1582] add tags details tests --- SoftLayer/fixtures/SoftLayer_Tag.py | 4 ++++ tests/CLI/modules/tag_tests.py | 16 ++++++++++++++-- tests/managers/tag_tests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index ca0d952ef..6839f7398 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -16,3 +16,7 @@ ] setTags = True + +getObject = getAttachedTagsForCurrentUser[0] + +getTagByTagName = getAttachedTagsForCurrentUser diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index 847511e80..f7eac8430 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -33,7 +33,7 @@ def test_set_tags(self, click): 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),) + args=("tag1,tag2", "GUEST", 100), ) @mock.patch('SoftLayer.CLI.tags.set.click') def test_set_tags_failure(self, click): @@ -43,4 +43,16 @@ def test_set_tags_failure(self, click): 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),) + 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) diff --git a/tests/managers/tag_tests.py b/tests/managers/tag_tests.py index f7ab940bb..e2dba99c9 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -111,3 +111,29 @@ def test_set_tags(self): 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) From 988185ab16cd656a22ca0749e93bf22d7983ec1a Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Jun 2020 19:23:25 -0400 Subject: [PATCH 0352/1582] add tags docs --- docs/cli/tags.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 5cca010f8..3aa9d75f9 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -7,3 +7,11 @@ Tag Commands .. 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: \ No newline at end of file From da3c5e27e9919fa64792cb08a3f8191664f7c861 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 5 Jun 2020 11:57:47 -0400 Subject: [PATCH 0353/1582] delete tags --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/delete.py | 30 +++++++++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 2 ++ SoftLayer/managers/tags.py | 3 +++ tests/CLI/modules/tag_tests.py | 9 +++++++++ 5 files changed, 45 insertions(+) create mode 100644 SoftLayer/CLI/tags/delete.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a71b62439..2ee597045 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -294,6 +294,7 @@ ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), + ('tags:delete', 'SoftLayer.CLI.tags.delete:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py new file mode 100644 index 000000000..5c6ccbc08 --- /dev/null +++ b/SoftLayer/CLI/tags/delete.py @@ -0,0 +1,30 @@ +"""List Tags.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + +# pylint: disable=unnecessary-lambda + +from pprint import pprint as pp + + +@click.command() +@click.option('-id', required=False, show_default=False, type=int, help='identifier') +@click.option('--name', required=False, default=False, type=str, show_default=False, help='tag name') +@environment.pass_env +def cli(env, id, name): + """delete Tag.""" + + tag_manager = TagManager(env.client) + + if not name and id is not None: + tag_name = tag_manager.get_tag(id) + tag_manager.delete_tag(tag_name['name']) + if name and id is None: + tag_manager.delete_tag(name) diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 6839f7398..ec4d1163e 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -15,6 +15,8 @@ } ] +deleteTag = True + setTags = True getObject = getAttachedTagsForCurrentUser[0] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index e5b43a60f..8752f5257 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -116,6 +116,9 @@ def reference_lookup(self, resource_table_id, tag_type): # return {} return self.client.call(service, 'getObject', id=resource_table_id) + def delete_tag(self, name): + return self.client.call('SoftLayer_Tag', 'deleteTag', name) + def set_tags(self, tags, key_name, resource_id): """Calls SoftLayer_Tag::setTags() diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index f7eac8430..ac6930d08 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -7,6 +7,7 @@ import mock from SoftLayer import testing +from SoftLayer.managers.tags import TagManager class TagCLITests(testing.TestCase): @@ -56,3 +57,11 @@ def test_details_by_id(self): 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', '--name="test"']) + self.assert_no_fail(result) + + def test_deleteTags_by_id(self): + result = self.run_command(['tags', 'delete', '-id=123456']) + self.assert_no_fail(result) From b4cff4282e79f7284955b2942bf575342f06eca9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 8 Jun 2020 16:52:43 -0500 Subject: [PATCH 0354/1582] #1230 added a taggable command, to list all things that are able to be tagged --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/delete.py | 37 +++++------ SoftLayer/CLI/tags/details.py | 9 ++- SoftLayer/CLI/tags/set.py | 6 +- SoftLayer/CLI/tags/taggable.py | 44 +++++++++++++ SoftLayer/managers/tags.py | 113 ++++++++++++++++++++++++++++----- 6 files changed, 172 insertions(+), 38 deletions(-) create mode 100644 SoftLayer/CLI/tags/taggable.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2ee597045..3c15fe772 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -295,6 +295,7 @@ ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), ('tags:delete', 'SoftLayer.CLI.tags.delete:cli'), + ('tags:taggable', 'SoftLayer.CLI.tags.taggable:cli'), ('ticket', 'SoftLayer.CLI.ticket'), ('ticket:create', 'SoftLayer.CLI.ticket.create:cli'), diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index 5c6ccbc08..f72e4687f 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -1,30 +1,31 @@ -"""List Tags.""" +"""Delete Tags.""" # :license: MIT, see LICENSE for more details. import click -from SoftLayer.exceptions import SoftLayerAPIError +from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting -from SoftLayer import utils - -# pylint: disable=unnecessary-lambda - -from pprint import pprint as pp @click.command() -@click.option('-id', required=False, show_default=False, type=int, help='identifier') -@click.option('--name', required=False, default=False, type=str, show_default=False, help='tag name') +@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, id, name): - """delete Tag.""" +def cli(env, identifier, name): + """Delete a Tag. Tag names that contain spaces need to be encased in quotes""" tag_manager = TagManager(env.client) - - if not name and id is not None: - tag_name = tag_manager.get_tag(id) - tag_manager.delete_tag(tag_name['name']) - if name and id is None: - tag_manager.delete_tag(name) + tag_name = identifier + # If the identifier is a int, and user didn't tell us it was a name. + if str.isdigit(identifier) and not name: + tag = tag_manager.get_tag(tag_id) + tag_name = tag.get('name', None) + + + result = tag_manager.delete_tag(tag_name) + if result: + click.secho("Tag {} has been removed".format(tag_name), fg='green') + else: + click.secho("Failed to remove tag {}".format(tag_name), fg='red') \ No newline at end of file diff --git a/SoftLayer/CLI/tags/details.py b/SoftLayer/CLI/tags/details.py index eb22bfada..7c397f431 100644 --- a/SoftLayer/CLI/tags/details.py +++ b/SoftLayer/CLI/tags/details.py @@ -10,13 +10,16 @@ @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): - """Get details for a Tag.""" +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 str.isdigit(identifier): + # 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) diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py index aa0879c6e..e30137ea5 100644 --- a/SoftLayer/CLI/tags/set.py +++ b/SoftLayer/CLI/tags/set.py @@ -8,8 +8,10 @@ @click.command() -@click.option('--tags', '-t', type=click.STRING, required=True, help='List of tags e.g. "tag1, tag2"') -@click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE") +@click.option('--tags', '-t', type=click.STRING, required=True, + help='Comma seperated list of tags, enclosed in quotes. "tag1, tag2"') +@click.option('--key-name', '-k', type=click.STRING, required=True, + help="Key name of a tag type e.g. GUEST, HARDWARE. See slcli tags taggable output.") @click.option('--resource-id', '-r', type=click.INT, required=True, help="ID of the object being tagged") @environment.pass_env def cli(env, tags, key_name, resource_id): diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py new file mode 100644 index 000000000..2fd9ef2b1 --- /dev/null +++ b/SoftLayer/CLI/tags/taggable.py @@ -0,0 +1,44 @@ +"""List everything that could be tagged.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.exceptions import ArgumentError +from SoftLayer.CLI import formatting +from SoftLayer.managers.tags import TagManager +from SoftLayer.CLI import environment + +from pprint import pprint as pp +@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'], + get_resource_name(resource['resource'], tag_type['keyName']) + ]) + env.fout(table) + + +def get_resource_name(resource, tag_type): + """Returns a string that names a resource""" + 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') \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 8752f5257..19fd077ec 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -96,9 +96,75 @@ def reference_lookup(self, resource_table_id, tag_type): |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 {} + 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']: - raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) + return None if tag_type == 'APPLICATION_DELIVERY_CONTROLLER': service = 'Network_Application_Delivery_Controller' @@ -106,24 +172,41 @@ def reference_lookup(self, resource_table_id, tag_type): 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 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 - # return {} - return self.client.call(service, 'getObject', id=resource_table_id) - - def delete_tag(self, name): - 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) From 793e375627c87e54de46fd5125294143bac3c9b7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 9 Jun 2020 15:38:35 -0500 Subject: [PATCH 0355/1582] #1230 added tag cleanup command --- SoftLayer/CLI/core.py | 1 + SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/tags/cleanup.py | 33 +++++++++++++++++++++++++++++++++ SoftLayer/CLI/tags/list.py | 10 ++++------ SoftLayer/CLI/tags/taggable.py | 18 +----------------- SoftLayer/managers/tags.py | 21 ++++++++++++++++++++- 6 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 SoftLayer/CLI/tags/cleanup.py diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 362ff72e7..7257c59d9 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -158,6 +158,7 @@ def cli(env, logger.setLevel(DEBUG_LOGGING_MAP.get(verbose, logging.DEBUG)) env.vars['_timings'] = SoftLayer.DebugTransport(env.client.transport) + env.vars['verbose'] = verbose env.client.transport = env.vars['_timings'] diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 3c15fe772..cc67a7d2f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -291,6 +291,7 @@ ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), ('tags', 'SoftLayer.CLI.tags'), + ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), ('tags:list', 'SoftLayer.CLI.tags.list:cli'), ('tags:set', 'SoftLayer.CLI.tags.set:cli'), ('tags:details', 'SoftLayer.CLI.tags.details:cli'), diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py new file mode 100644 index 000000000..d7dd0fc80 --- /dev/null +++ b/SoftLayer/CLI/tags/cleanup.py @@ -0,0 +1,33 @@ +"""Removes unused 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 + +from pprint import pprint as pp +# pylint: disable=unnecessary-lambda + + +@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/list.py b/SoftLayer/CLI/tags/list.py index e2f136581..e73ffe718 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -66,13 +66,11 @@ def simple_table(tag_manager): 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) - if tag_type == 'NETWORK_VLAN_FIREWALL': - resource_row = resource.get('primaryIpAddress') - else: - resource_row = resource.get('fullyQualifiedDomainName') + name = tag_manager.get_resource_name(resource, tag_type) except SoftLayerAPIError as exception: - resource_row = "{}".format(exception.reason) - return resource_row + name = "{}".format(exception.reason) + return name diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 2fd9ef2b1..8436cae52 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -23,22 +23,6 @@ def cli(env): for resource in resources: table.add_row([ resource['resource']['id'], - get_resource_name(resource['resource'], tag_type['keyName']) + tag_manager.get_resource_name(resource['resource'], tag_type['keyName']) ]) env.fout(table) - - -def get_resource_name(resource, tag_type): - """Returns a string that names a resource""" - 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') \ No newline at end of file diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 19fd077ec..76df6ba43 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -99,7 +99,6 @@ def reference_lookup(self, resource_table_id, tag_type): service = self.type_to_service(tag_type) if service is None: raise SoftLayerAPIError(404, "Unable to lookup {} types".format(tag_type)) - # return {} return self.client.call(service, 'getObject', id=resource_table_id) def delete_tag(self, name): @@ -182,6 +181,26 @@ def type_to_service(tag_type): 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""" From 5a61a95421ac197ec584363986021792b2575a98 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 9 Jun 2020 16:27:19 -0500 Subject: [PATCH 0356/1582] CLI unit tests --- SoftLayer/CLI/tags/delete.py | 2 +- SoftLayer/CLI/tags/taggable.py | 2 -- SoftLayer/fixtures/SoftLayer_Search.py | 23 +++++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Tag.py | 7 +++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Search.py diff --git a/SoftLayer/CLI/tags/delete.py b/SoftLayer/CLI/tags/delete.py index f72e4687f..d6957af6b 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -20,7 +20,7 @@ def cli(env, identifier, name): tag_name = identifier # If the identifier is a int, and user didn't tell us it was a name. if str.isdigit(identifier) and not name: - tag = tag_manager.get_tag(tag_id) + tag = tag_manager.get_tag(identifier) tag_name = tag.get('name', None) diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 8436cae52..304f7007d 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -3,12 +3,10 @@ import click -from SoftLayer.CLI.exceptions import ArgumentError from SoftLayer.CLI import formatting from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment -from pprint import pprint as pp @click.command() @environment.pass_env def cli(env): diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py new file mode 100644 index 000000000..77fe52033 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -0,0 +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" + } + } + } +] \ No newline at end of file diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index ec4d1163e..5bbccf243 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -22,3 +22,10 @@ getObject = getAttachedTagsForCurrentUser[0] getTagByTagName = getAttachedTagsForCurrentUser + +getAllTagTypes = [ + { + "description": "Hardware", + "keyName": "HARDWARE" + } +] \ No newline at end of file From a13fdd0b5b6945def79ccd2ce840bf1cc59fa714 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Jun 2020 16:36:19 -0500 Subject: [PATCH 0357/1582] #1230 more unit tests, code cleanup --- SoftLayer/CLI/tags/cleanup.py | 9 +--- SoftLayer/CLI/tags/delete.py | 6 +-- SoftLayer/CLI/tags/list.py | 1 - SoftLayer/CLI/tags/set.py | 2 +- SoftLayer/CLI/tags/taggable.py | 3 +- SoftLayer/fixtures/SoftLayer_Search.py | 2 +- SoftLayer/fixtures/SoftLayer_Tag.py | 2 +- SoftLayer/managers/tags.py | 7 ++- docs/cli/tags.rst | 11 +++- tests/CLI/modules/tag_tests.py | 60 +++++++++++++++++++--- tests/managers/tag_tests.py | 69 ++++++++++++++++++++++++++ 11 files changed, 143 insertions(+), 29 deletions(-) diff --git a/SoftLayer/CLI/tags/cleanup.py b/SoftLayer/CLI/tags/cleanup.py index d7dd0fc80..26ddea7ef 100644 --- a/SoftLayer/CLI/tags/cleanup.py +++ b/SoftLayer/CLI/tags/cleanup.py @@ -4,13 +4,7 @@ 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 - -from pprint import pprint as pp -# pylint: disable=unnecessary-lambda @click.command() @@ -22,7 +16,7 @@ def cli(env, dry_run): 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') @@ -30,4 +24,3 @@ def cli(env, dry_run): 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/delete.py b/SoftLayer/CLI/tags/delete.py index d6957af6b..f3bb1e70b 100644 --- a/SoftLayer/CLI/tags/delete.py +++ b/SoftLayer/CLI/tags/delete.py @@ -3,9 +3,8 @@ import click -from SoftLayer.CLI.exceptions import ArgumentError -from SoftLayer.managers.tags import TagManager from SoftLayer.CLI import environment +from SoftLayer.managers.tags import TagManager @click.command() @@ -23,9 +22,8 @@ def cli(env, identifier, name): tag = tag_manager.get_tag(identifier) tag_name = tag.get('name', None) - result = tag_manager.delete_tag(tag_name) if result: click.secho("Tag {} has been removed".format(tag_name), fg='green') else: - click.secho("Failed to remove tag {}".format(tag_name), fg='red') \ No newline at end of file + click.secho("Failed to remove tag {}".format(tag_name), fg='red') diff --git a/SoftLayer/CLI/tags/list.py b/SoftLayer/CLI/tags/list.py index e73ffe718..bc8662764 100644 --- a/SoftLayer/CLI/tags/list.py +++ b/SoftLayer/CLI/tags/list.py @@ -73,4 +73,3 @@ def get_resource_name(tag_manager, resource_id, tag_type): except SoftLayerAPIError as exception: name = "{}".format(exception.reason) return name - diff --git a/SoftLayer/CLI/tags/set.py b/SoftLayer/CLI/tags/set.py index e30137ea5..ed409fb99 100644 --- a/SoftLayer/CLI/tags/set.py +++ b/SoftLayer/CLI/tags/set.py @@ -8,7 +8,7 @@ @click.command() -@click.option('--tags', '-t', type=click.STRING, required=True, +@click.option('--tags', '-t', type=click.STRING, required=True, help='Comma seperated list of tags, enclosed in quotes. "tag1, tag2"') @click.option('--key-name', '-k', type=click.STRING, required=True, help="Key name of a tag type e.g. GUEST, HARDWARE. See slcli tags taggable output.") diff --git a/SoftLayer/CLI/tags/taggable.py b/SoftLayer/CLI/tags/taggable.py index 304f7007d..0c08acdb0 100644 --- a/SoftLayer/CLI/tags/taggable.py +++ b/SoftLayer/CLI/tags/taggable.py @@ -3,9 +3,10 @@ import click +from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.tags import TagManager -from SoftLayer.CLI import environment + @click.command() @environment.pass_env diff --git a/SoftLayer/fixtures/SoftLayer_Search.py b/SoftLayer/fixtures/SoftLayer_Search.py index 77fe52033..ccb45fe55 100644 --- a/SoftLayer/fixtures/SoftLayer_Search.py +++ b/SoftLayer/fixtures/SoftLayer_Search.py @@ -20,4 +20,4 @@ } } } -] \ No newline at end of file +] diff --git a/SoftLayer/fixtures/SoftLayer_Tag.py b/SoftLayer/fixtures/SoftLayer_Tag.py index 5bbccf243..9f6aeaec4 100644 --- a/SoftLayer/fixtures/SoftLayer_Tag.py +++ b/SoftLayer/fixtures/SoftLayer_Tag.py @@ -28,4 +28,4 @@ "description": "Hardware", "keyName": "HARDWARE" } -] \ No newline at end of file +] diff --git a/SoftLayer/managers/tags.py b/SoftLayer/managers/tags.py index 76df6ba43..818b0547d 100644 --- a/SoftLayer/managers/tags.py +++ b/SoftLayer/managers/tags.py @@ -120,7 +120,7 @@ def set_tags(self, tags, key_name, resource_id): def get_all_tag_types(self): """Calls SoftLayer_Tag::getAllTagTypes()""" types = self.client.call('SoftLayer_Tag', 'getAllTagTypes') - useable_types = [] + 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. @@ -146,14 +146,14 @@ def taggable_by_type(self, tag_type): to_return = [] # Fake search result output for resource in resources: - to_return.append({'resourceType':service, 'resource':resource}) + 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}) + to_return.append({'resourceType': service, 'resource': resource}) return to_return resources = self.client.call('SoftLayer_Search', 'advancedSearch', search_term, iter=True) return resources @@ -228,4 +228,3 @@ def get_resource_name(resource, tag_type): # datatye = 'blockDeviceTemplateGroups' # return datatye - diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 3aa9d75f9..435d4dc59 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -3,6 +3,7 @@ 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 @@ -14,4 +15,12 @@ Tag Commands .. click:: SoftLayer.CLI.tags.details:cli :prog: tags details - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.tags.delete:cli + :prog: tags delete + :show-nested: + +.. click:: SoftLayer.CLI.tags.taggable:cli + :prog: tags taggable + :show-nested: diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index ac6930d08..b2e29721e 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -6,8 +6,8 @@ """ import mock +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import testing -from SoftLayer.managers.tags import TagManager class TagCLITests(testing.TestCase): @@ -28,13 +28,23 @@ def test_list_detail(self): 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), ) + 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): @@ -43,8 +53,7 @@ def test_set_tags_failure(self, click): 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), ) + self.assert_called_with('SoftLayer_Tag', 'setTags', args=("tag1,tag2", "GUEST", 100), ) def test_details_by_name(self): tag_name = 'bs_test_instance' @@ -59,9 +68,46 @@ def test_details_by_id(self): self.assert_called_with('SoftLayer_Tag', 'getObject', identifier=tag_id) def test_deleteTags_by_name(self): - result = self.run_command(['tags', 'delete', '--name="test"']) + 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', '-id=123456']) + 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 e2dba99c9..67c817a6f 100644 --- a/tests/managers/tag_tests.py +++ b/tests/managers/tag_tests.py @@ -137,3 +137,72 @@ def test_get_tag_by_name_mask(self): 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 39627a9aca5f3fc4425107887456dde9ea7524c6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 10 Jun 2020 16:42:35 -0500 Subject: [PATCH 0358/1582] Added slcli tags clenaup documentation --- docs/cli/tags.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/tags.rst b/docs/cli/tags.rst index 435d4dc59..a5a99b694 100644 --- a/docs/cli/tags.rst +++ b/docs/cli/tags.rst @@ -24,3 +24,7 @@ These commands will allow you to interact with the **IMS** provier tagging servi .. click:: SoftLayer.CLI.tags.taggable:cli :prog: tags taggable :show-nested: + +.. click:: SoftLayer.CLI.tags.cleanup:cli + :prog: tags cleanup + :show-nested: From c293f4ba7467099c1d08c5bff0846a25216c709a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:16:48 -0400 Subject: [PATCH 0359/1582] Implement slcli vlan edit functionality. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/edit.py | 41 ++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 3 ++ SoftLayer/managers/network.py | 40 +++++++++++++++++++ tests/CLI/modules/vlan_tests.py | 18 +++++++++ tests/managers/network_tests.py | 9 +++++ 6 files changed, 112 insertions(+) create mode 100644 SoftLayer/CLI/vlan/edit.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc67a7d2f..604841144 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -322,6 +322,7 @@ ('vlan', 'SoftLayer.CLI.vlan'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), + ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), ('summary', 'SoftLayer.CLI.summary:cli'), diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py new file mode 100644 index 000000000..20a004921 --- /dev/null +++ b/SoftLayer/CLI/vlan/edit.py @@ -0,0 +1,41 @@ +"""Edit a vlan's details.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment, exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--name', '-n', + help="The optional name for this VLAN") +@click.option('--note', '-e', + help="The note for this vlan.") +@click.option('--tags', '-g', + multiple=True, + help='Tags to set e.g. "tag1,tag2", or empty string to remove all' + ) +@environment.pass_env +def cli(env, identifier, name, note, tags): + """Edit a vlan's details.""" + + data = { + 'name': name, + 'note': note + } + + if tags: + data['tags'] = ','.join(tags) + + mgr = SoftLayer.NetworkManager(env.client) + vlan_id = helpers.resolve_id(mgr.resolve_vlan_ids, identifier, 'VLAN') + vlan = mgr.edit(vlan_id, **data) + + if vlan: + click.secho("Vlan edited successfully", fg='green') + else: + click.secho("Failed to edit the vlan", fg='red') diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 5c7d7232a..b18632534 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -7,3 +7,6 @@ 'vlanNumber': 4444, 'firewallInterfaces': None } + +editObject = True +setTags = True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index dbfb9c3f6..49dde11d7 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -7,12 +7,17 @@ """ import collections import json +import logging + +from SoftLayer.decoration import retry from SoftLayer import exceptions from SoftLayer import utils from SoftLayer.managers import event_log +LOGGER = logging.getLogger(__name__) + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', @@ -685,3 +690,38 @@ def get_nas_credentials(self, identifier, **kwargs): """ result = self.network_storage.getObject(id=identifier, **kwargs) return result + + def edit(self, instance_id, name=None, note=None, tags=None): + """Edit a vlan. + + :param integer instance_id: the instance ID to edit. + :param string name: valid name. + :param string note: note about this particular v;am. + :param string tags: tags to set on the vlan as a comma separated list. + Use the empty string to remove all tags. + :returns: bool -- True or an Exception + """ + + obj = {} + + if tags is not None: + self.set_tags(tags, vlan_id=instance_id) + + if name: + obj['name'] = name + + if note: + obj['note'] = note + + if not obj: + return True + + return self.vlan.editObject(obj, id=instance_id) + + @retry(logger=LOGGER) + def set_tags(self, tags, vlan_id): + """Sets tags on a vlan with a retry decorator + + Just calls vlan.setTags, but if it fails from an APIError will retry. + """ + self.vlan.setTags(tags, id=vlan_id) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 86b15507a..1cdd3f3f5 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,6 +4,8 @@ :license: MIT, see LICENSE for more details. """ +import mock + from SoftLayer import testing @@ -76,3 +78,19 @@ def test_detail_hardware_without_hostname(self): vlan_mock.return_value = getObject result = self.run_command(['vlan', 'detail', '1234']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.vlan.edit.click') + def test_vlan_edit(self, click): + result = self.run_command(['vlan', 'edit', '--name=nameTest', '--note=noteTest', '--tags=tag1,tag2', '100']) + click.secho.assert_called_with('Vlan edited successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + + @mock.patch('SoftLayer.CLI.vlan.edit.click') + def test_vlan_edit_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Vlan', 'editObject') + mock.return_value = False + result = self.run_command(['vlan', 'edit', '--name=nameTest', '--note=noteTest', '--tags=tag1,tag2', '100']) + click.secho.assert_called_with('Failed to edit the vlan', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index fd986e2ea..165d04b71 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -625,3 +625,12 @@ def test_get_cci_event_logs(self): _filter = {'objectName': {'operation': 'CCI'}} self.assert_called_with('SoftLayer_Event_Log', 'getAllObjects', filter=_filter) self.assertEqual(100, log['accountId']) + + def test_vlan_edit(self): + vlan_id = 100 + name = "test" + note = "test note" + tags = "tag1,tag2" + + self.network.edit(vlan_id, name, note, tags) + self.assert_called_with('SoftLayer_Network_Vlan', 'editObject') From 303a63072b4a967203d333105d367e5398f7859c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:31:49 -0400 Subject: [PATCH 0360/1582] Add documentation and fix tox analysis. --- SoftLayer/CLI/vlan/edit.py | 3 +-- docs/cli/vlan.rst | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index 20a004921..3d043615d 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -4,8 +4,7 @@ import click import SoftLayer -from SoftLayer.CLI import environment, exceptions -from SoftLayer.CLI import formatting +from SoftLayer.CLI import environment from SoftLayer.CLI import helpers diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 1733f40e4..6fc084da7 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -7,6 +7,10 @@ VLANs :prog: vlan detail :show-nested: +.. click:: SoftLayer.CLI.vlan.edit:cli + :prog: vlan edit + :show-nested: + .. click:: SoftLayer.CLI.vlan.list:cli :prog: vlan list :show-nested: From 38d4dd49bdcb391854f33bc5251a80e6d2cbc065 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 17:59:43 -0400 Subject: [PATCH 0361/1582] Fix tox analysis. --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 49dde11d7..4c22aa678 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -18,6 +18,8 @@ LOGGER = logging.getLogger(__name__) +# pylint: disable=no-self-use,too-many-lines + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', From b5a8dd00acbaf11b43a9595da78430995ef07bd2 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 18 Jun 2020 18:02:51 -0400 Subject: [PATCH 0362/1582] Fix tox analysis. --- SoftLayer/managers/network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 4c22aa678..abe625f85 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -18,7 +18,7 @@ LOGGER = logging.getLogger(__name__) -# pylint: disable=no-self-use,too-many-lines +# pylint: disable=too-many-public-methods DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', From 6f821872878cc866cb35df6793a10c145167fb6e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:08:04 -0400 Subject: [PATCH 0363/1582] new feature edit ip note and add ipAddress table in detail --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/detail.py | 13 ++++++++- SoftLayer/CLI/subnet/edit_ip.py | 27 +++++++++++++++++++ .../fixtures/SoftLayer_Network_Subnet.py | 7 ++++- .../SoftLayer_Network_Subnet_IpAddress.py | 2 ++ SoftLayer/managers/network.py | 5 ++++ tests/CLI/modules/subnet_tests.py | 8 ++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/subnet/edit_ip.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index cc67a7d2f..fe492d46e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -289,6 +289,7 @@ ('subnet:detail', 'SoftLayer.CLI.subnet.detail:cli'), ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), + ('subnet:edit-ip', 'SoftLayer.CLI.subnet.edit_ip:cli'), ('tags', 'SoftLayer.CLI.tags'), ('tags:cleanup', 'SoftLayer.CLI.tags.cleanup:cli'), diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 1c8f7e2dc..39dbb700d 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -25,7 +25,10 @@ def cli(env, identifier, no_vs, no_hardware): mgr = SoftLayer.NetworkManager(env.client) subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, name='subnet') - subnet = mgr.get_subnet(subnet_id) + + mask = 'mask[ipAddresses[id, ipAddress], datacenter, virtualGuests,hardware]' + + subnet = mgr.get_subnet(subnet_id, mask=mask) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -45,6 +48,14 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['usable ips', subnet.get('usableIpAddressCount', formatting.blank())]) + ip_address = subnet.get('ipAddresses') + + ip_table = formatting.KeyValueTable(['ipAddress','value']) + for address in ip_address: + ip_table.add_row([address.get('id'),address.get('ipAddress')]) + + table.add_row(['ipAddresses', ip_table]) + if not no_vs: if subnet['virtualGuests']: vs_table = formatting.Table(['hostname', 'domain', 'public_ip', 'private_ip']) diff --git a/SoftLayer/CLI/subnet/edit_ip.py b/SoftLayer/CLI/subnet/edit_ip.py new file mode 100644 index 000000000..00077ad2c --- /dev/null +++ b/SoftLayer/CLI/subnet/edit_ip.py @@ -0,0 +1,27 @@ +"""Edit ip note""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.argument('identifier') +@click.option('--ip', required=True, + help='Assume the ipAddress to set the note.') +@click.option('--note', help="set ip address note of subnet") +@environment.pass_env +def cli(env, identifier, ip, note): + """Set the note of the ipAddress subnet""" + + data = { + 'note': note + } + mgr = SoftLayer.NetworkManager(env.client) + ips = mgr.get_subnet(identifier, mask='id,ipAddresses[id,ipAddress]').get('ipAddresses') + + for address in ips: + if ip == address.get('ipAddress'): + mgr.set_subnet_ipddress_note(address.get('id'), data) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 7fc1e34dd..c6658165a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -25,5 +25,10 @@ } ], 'hardware': [], - 'usableIpAddressCount': 22 + 'usableIpAddressCount': 22, + 'ipAddresses': [ + {'id': 123456, + 'ipAddress': '16.26.26.25'}, + {'id': 123457, + 'ipAddress': '16.26.26.26'}] } diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index d7ed9749d..5274ffeda 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -7,3 +7,5 @@ 'isReserved': False, 'subnetId': 5678, } + +editObject= True diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index dbfb9c3f6..786f45f4c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -685,3 +685,8 @@ def get_nas_credentials(self, identifier, **kwargs): """ result = self.network_storage.getObject(id=identifier, **kwargs) return result + + def set_subnet_ipddress_note(self, identifier, note): + """Set the ip address note of the subnet""" + result = self.client.call('SoftLayer_Network_Subnet_IpAddress','editObject', note, id=identifier) + return result \ No newline at end of file diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 1971aa420..650161dea 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,6 +36,9 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], + 'ipAddresses': { + '123456': '16.26.26.25', + '123457': '16.26.26.26'}, 'hardware': 'none', 'usable ips': 22 }, @@ -134,3 +137,8 @@ def test_create_subnet_static_ipv6(self, confirm_mock): ] self.assertEqual(output, json.loads(result.output)) + + def test_editrou_Ip(self): + result = self.run_command(['subnet', 'edit-ip', '123456', '--ip=16.26.26.26', '--note=test']) + self.assert_no_fail(result) + self.assertTrue(result) From 2d55c6f039a45ff6c75fef65d8994cfa776018ee Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:15:26 -0400 Subject: [PATCH 0364/1582] fix tox tool --- SoftLayer/CLI/subnet/detail.py | 4 ++-- docs/cli/subnet.rst | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 39dbb700d..e965df55d 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -50,9 +50,9 @@ def cli(env, identifier, no_vs, no_hardware): ip_address = subnet.get('ipAddresses') - ip_table = formatting.KeyValueTable(['ipAddress','value']) + ip_table = formatting.KeyValueTable(['ipAddress', 'value']) for address in ip_address: - ip_table.add_row([address.get('id'),address.get('ipAddress')]) + ip_table.add_row([address.get('id'), address.get('ipAddress')]) table.add_row(['ipAddresses', ip_table]) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 20fce0def..5ba25c1f3 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -22,3 +22,7 @@ Subnets .. click:: SoftLayer.CLI.subnet.lookup:cli :prog: subnet lookup :show-nested: + +.. click:: SoftLayer.CLI.subnet.edit-ip:cli + :prog: subnet edit-ip + :show-nested: From 520d4f68494b531f0f8075427672978f76231d8d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:20:57 -0400 Subject: [PATCH 0365/1582] fix tox tool --- SoftLayer/managers/network.py | 2 +- docs/cli/subnet.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 786f45f4c..fa26ca04b 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -688,5 +688,5 @@ def get_nas_credentials(self, identifier, **kwargs): def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" - result = self.client.call('SoftLayer_Network_Subnet_IpAddress','editObject', note, id=identifier) + result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result \ No newline at end of file diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 5ba25c1f3..124f36cde 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -23,6 +23,6 @@ Subnets :prog: subnet lookup :show-nested: -.. click:: SoftLayer.CLI.subnet.edit-ip:cli +.. click:: SoftLayer.CLI.subnet.edit_ip:cli :prog: subnet edit-ip :show-nested: From a24dcfcba8201353f383f97a2327908c234b6a6d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:24:41 -0400 Subject: [PATCH 0366/1582] fix tox tool --- SoftLayer/managers/network.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index fa26ca04b..a2fef4e5a 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -689,4 +689,5 @@ def get_nas_credentials(self, identifier, **kwargs): def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) - return result \ No newline at end of file + return result + \ No newline at end of file From b6cd35aba9459579dad69c385dd029c57f5a6c14 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 19 Jun 2020 20:28:37 -0400 Subject: [PATCH 0367/1582] fix tox tool --- SoftLayer/managers/network.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index a2fef4e5a..cac9b630c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -690,4 +690,3 @@ def set_subnet_ipddress_note(self, identifier, note): """Set the ip address note of the subnet""" result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result - \ No newline at end of file From 38e61a2a4245de9bb07f68a9cb05e00e0bfa4b36 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 22 Jun 2020 09:59:19 -0400 Subject: [PATCH 0368/1582] Refactor vlan edit functionality. --- SoftLayer/CLI/vlan/edit.py | 9 +++------ SoftLayer/managers/network.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/edit.py b/SoftLayer/CLI/vlan/edit.py index 3d043615d..c623e5d2d 100644 --- a/SoftLayer/CLI/vlan/edit.py +++ b/SoftLayer/CLI/vlan/edit.py @@ -22,17 +22,14 @@ def cli(env, identifier, name, note, tags): """Edit a vlan's details.""" - data = { - 'name': name, - 'note': note - } + new_tags = None if tags: - data['tags'] = ','.join(tags) + new_tags = ','.join(tags) mgr = SoftLayer.NetworkManager(env.client) vlan_id = helpers.resolve_id(mgr.resolve_vlan_ids, identifier, 'VLAN') - vlan = mgr.edit(vlan_id, **data) + vlan = mgr.edit(vlan_id, name=name, note=note, tags=new_tags) if vlan: click.secho("Vlan edited successfully", fg='green') diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index abe625f85..b3f6f7b6d 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -698,7 +698,7 @@ def edit(self, instance_id, name=None, note=None, tags=None): :param integer instance_id: the instance ID to edit. :param string name: valid name. - :param string note: note about this particular v;am. + :param string note: note about this particular vlan. :param string tags: tags to set on the vlan as a comma separated list. Use the empty string to remove all tags. :returns: bool -- True or an Exception From cc4c6ed2c73e5b4042fcc1ee7ce13cd28b428929 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Jun 2020 14:22:26 -0400 Subject: [PATCH 0369/1582] fix tox tool --- SoftLayer/CLI/subnet/edit_ip.py | 6 +++--- SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py | 2 +- tests/CLI/modules/subnet_tests.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/subnet/edit_ip.py b/SoftLayer/CLI/subnet/edit_ip.py index 00077ad2c..8cf65cb9b 100644 --- a/SoftLayer/CLI/subnet/edit_ip.py +++ b/SoftLayer/CLI/subnet/edit_ip.py @@ -9,11 +9,11 @@ @click.command() @click.argument('identifier') -@click.option('--ip', required=True, +@click.option('--ip-address', required=True, help='Assume the ipAddress to set the note.') @click.option('--note', help="set ip address note of subnet") @environment.pass_env -def cli(env, identifier, ip, note): +def cli(env, identifier, ip_address, note): """Set the note of the ipAddress subnet""" data = { @@ -23,5 +23,5 @@ def cli(env, identifier, ip, note): ips = mgr.get_subnet(identifier, mask='id,ipAddresses[id,ipAddress]').get('ipAddresses') for address in ips: - if ip == address.get('ipAddress'): + if ip_address == address.get('ipAddress'): mgr.set_subnet_ipddress_note(address.get('id'), data) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index 5274ffeda..15778d238 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -8,4 +8,4 @@ 'subnetId': 5678, } -editObject= True +editObject = True diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 650161dea..3489ec97e 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,9 +36,9 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], - 'ipAddresses': { - '123456': '16.26.26.25', - '123457': '16.26.26.26'}, + 'ipAddresses': { + '123456': '16.26.26.25', + '123457': '16.26.26.26'}, 'hardware': 'none', 'usable ips': 22 }, @@ -139,6 +139,6 @@ def test_create_subnet_static_ipv6(self, confirm_mock): self.assertEqual(output, json.loads(result.output)) def test_editrou_Ip(self): - result = self.run_command(['subnet', 'edit-ip', '123456', '--ip=16.26.26.26', '--note=test']) + result = self.run_command(['subnet', 'edit-ip', '123456', '--ip-address=16.26.26.26', '--note=test']) self.assert_no_fail(result) self.assertTrue(result) From d2e3958d11b6f0ea41080eb465aab07bde07eb61 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Jun 2020 14:42:06 -0400 Subject: [PATCH 0370/1582] fix tox tool --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index cac9b630c..73ea5fc0f 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -13,6 +13,8 @@ from SoftLayer.managers import event_log +# pylint: disable=too-many-public-methods + DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', 'ipAddressCount', From 4de733f533d900863ef6e66f2d9c861c78acfd74 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:39:02 -0400 Subject: [PATCH 0371/1582] add tags and note to subnet detail --- SoftLayer/CLI/subnet/detail.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 1c8f7e2dc..2758838ee 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -44,6 +44,10 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['datacenter', subnet['datacenter']['name']]) table.add_row(['usable ips', subnet.get('usableIpAddressCount', formatting.blank())]) + table.add_row(['note', + subnet.get('note', formatting.blank())]) + table.add_row(['tags', + formatting.tags(subnet.get('tagReferences'))]) if not no_vs: if subnet['virtualGuests']: @@ -55,7 +59,7 @@ def cli(env, identifier, no_vs, no_hardware): vsi.get('primaryBackendIpAddress')]) table.add_row(['vs', vs_table]) else: - table.add_row(['vs', 'none']) + table.add_row(['vs', formatting.blank()]) if not no_hardware: if subnet['hardware']: @@ -67,6 +71,6 @@ def cli(env, identifier, no_vs, no_hardware): hardware.get('primaryBackendIpAddress')]) table.add_row(['hardware', hw_table]) else: - table.add_row(['hardware', 'none']) + table.add_row(['hardware', formatting.blank()]) env.fout(table) From a8a50581528c64315e446481c445a647d8c81fa4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:41:55 -0400 Subject: [PATCH 0372/1582] add subnet edit --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/subnet/edit.py | 38 +++++++++++++++++++++++++++++++++++ SoftLayer/managers/network.py | 23 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 SoftLayer/CLI/subnet/edit.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 604841144..e3aa99b0a 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -286,6 +286,7 @@ ('subnet', 'SoftLayer.CLI.subnet'), ('subnet:cancel', 'SoftLayer.CLI.subnet.cancel:cli'), ('subnet:create', 'SoftLayer.CLI.subnet.create:cli'), + ('subnet:edit', 'SoftLayer.CLI.subnet.edit:cli'), ('subnet:detail', 'SoftLayer.CLI.subnet.detail:cli'), ('subnet:list', 'SoftLayer.CLI.subnet.list:cli'), ('subnet:lookup', 'SoftLayer.CLI.subnet.lookup:cli'), diff --git a/SoftLayer/CLI/subnet/edit.py b/SoftLayer/CLI/subnet/edit.py new file mode 100644 index 000000000..57856c57b --- /dev/null +++ b/SoftLayer/CLI/subnet/edit.py @@ -0,0 +1,38 @@ +"""Edit a subnet.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command(short_help="Edit note and tags of a subnet") +@click.argument('identifier') +@click.option('--tags', '-t', type=click.STRING, + help='Comma separated list of tags, enclosed in quotes. "tag1, tag2"') +@click.option('--note', '-n', type=click.STRING, + help="The note") +@environment.pass_env +def cli(env, identifier, tags, note): + """Edit note and tags of a subnet.""" + + mgr = SoftLayer.NetworkManager(env.client) + subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, + name='subnet') + + if tags: + result = mgr.set_tags_subnet(subnet_id, tags) + print_result(result, "Set tags") + + if note: + result = mgr.edit_note_subnet(subnet_id, note) + print_result(result, "Edit note") + + +def print_result(result, detail): + if result: + click.secho("{} successfully".format(detail), fg='green') + else: + click.secho("Failed to {}".format(detail.lower()), fg='red') diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index b3f6f7b6d..596e4777c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -24,6 +24,15 @@ 'datacenter', 'ipAddressCount', 'virtualGuests', + 'id', + 'networkIdentifier', + 'cidr', + 'subnetType', + 'gateway', + 'broadcastAddress', + 'usableIpAddressCount', + 'note', + 'tagReferences[tag]', 'networkVlan[id,networkSpace]']) DEFAULT_VLAN_MASK = ','.join([ 'firewallInterfaces', @@ -233,6 +242,20 @@ def cancel_subnet(self, subnet_id): billing_id = subnet['billingItem']['id'] return self.client['Billing_Item'].cancelService(id=billing_id) + def set_tags_subnet(self, subnet_id, tags): + """Tag a subnet by passing in one or more tags separated by a comma. + + :param int subnet_id: The ID of the subnet. + """ + return self.subnet.setTags(tags, id=subnet_id) + + def edit_note_subnet(self, subnet_id, note): + """Edit the note for this subnet. + + :param int subnet_id: The ID of the subnet. + """ + return self.subnet.editNote(note, id=subnet_id) + def create_securitygroup(self, name=None, description=None): """Creates a security group. From bc29ec3940511996958c6d9e30f99d8e186b1456 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:43:09 -0400 Subject: [PATCH 0373/1582] add subnet edit tests --- .../fixtures/SoftLayer_Network_Subnet.py | 13 +++++- tests/CLI/modules/subnet_tests.py | 40 ++++++++++++++++++- tests/managers/network_tests.py | 20 ++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 7fc1e34dd..d8c20fbc0 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -25,5 +25,16 @@ } ], 'hardware': [], - 'usableIpAddressCount': 22 + 'usableIpAddressCount': 22, + 'note': 'test note', + 'tagReferences': [ + {'id': 1000123, + 'resourceTableId': 1234, + 'tag': {'id': 100123, + 'name': 'subnet: test tag'}, + } + ] } + +editNote = True +setTags = True diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 1971aa420..493b30c83 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -36,8 +36,12 @@ def test_detail(self): 'private_ip': '10.0.1.2' } ], - 'hardware': 'none', - 'usable ips': 22 + 'hardware': None, + 'usable ips': 22, + 'note': 'test note', + 'tags': [ + 'subnet: test tag' + ], }, json.loads(result.output)) @@ -134,3 +138,35 @@ def test_create_subnet_static_ipv6(self, confirm_mock): ] self.assertEqual(output, json.loads(result.output)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_set_tags(self, click): + result = self.run_command(['subnet', 'edit', '1234', '--tags=tag1,tag2']) + click.secho.assert_called_with('Set tags successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', identifier=1234, args=("tag1,tag2",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_edit_note(self, click): + result = self.run_command(['subnet', 'edit', '1234', '--note=test']) + click.secho.assert_called_with('Edit note successfully', fg='green') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', identifier=1234, args=("test",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_subnet_set_tags_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Subnet', 'setTags') + mock.return_value = False + result = self.run_command(['subnet', 'edit', '1234', '--tags=tag1,tag2']) + click.secho.assert_called_with('Failed to set tags', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', identifier=1234, args=("tag1,tag2",)) + + @mock.patch('SoftLayer.CLI.subnet.edit.click') + def test_edit_note_failure(self, click): + mock = self.set_mock('SoftLayer_Network_Subnet', 'editNote') + mock.return_value = False + result = self.run_command(['subnet', 'edit', '1234', '--note=test']) + click.secho.assert_called_with('Failed to edit note', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', identifier=1234, args=("test",)) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 165d04b71..57106ecee 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -158,6 +158,26 @@ def test_cancel_subnet(self): self.assert_called_with('SoftLayer_Billing_Item', 'cancelService', identifier=1056) + def test_set_tags_subnet(self): + subnet_id = 1234 + tags = 'tags1,tag2' + result = self.network.set_tags_subnet(subnet_id, tags) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Network_Subnet', 'setTags', + identifier=subnet_id, + args=(tags,)) + + def test_edit_note_subnet(self): + subnet_id = 1234 + note = 'test note' + result = self.network.edit_note_subnet(subnet_id, note) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Network_Subnet', 'editNote', + identifier=subnet_id, + args=(note,)) + def test_create_securitygroup(self): result = self.network.create_securitygroup(name='foo', description='bar') From 5e0068429f05e32ab47ce45cf3c8fe032e0c06f9 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 18:44:08 -0400 Subject: [PATCH 0374/1582] add subnet edit docs --- docs/cli/subnet.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/subnet.rst b/docs/cli/subnet.rst index 20fce0def..1ad3c379c 100644 --- a/docs/cli/subnet.rst +++ b/docs/cli/subnet.rst @@ -15,6 +15,10 @@ Subnets :prog: subnet detail :show-nested: +.. click:: SoftLayer.CLI.subnet.edit:cli + :prog: subnet edit + :show-nested: + .. click:: SoftLayer.CLI.subnet.list:cli :prog: subnet list :show-nested: From ca7cc9c76c8873ca491fb28d35406706f03fbf41 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 19 Jun 2020 19:07:07 -0400 Subject: [PATCH 0375/1582] fix tox analysis and conflicts --- SoftLayer/CLI/subnet/edit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/subnet/edit.py b/SoftLayer/CLI/subnet/edit.py index 57856c57b..9a62692b1 100644 --- a/SoftLayer/CLI/subnet/edit.py +++ b/SoftLayer/CLI/subnet/edit.py @@ -32,6 +32,7 @@ def cli(env, identifier, tags, note): def print_result(result, detail): + """Prints a successfully or Failed message.""" if result: click.secho("{} successfully".format(detail), fg='green') else: From ae8bba254781c1b3b5d3f2a766f37b536c766d07 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 22 Jun 2020 20:16:02 -0400 Subject: [PATCH 0376/1582] add docs param for set tag and edit note subnet --- SoftLayer/managers/network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 596e4777c..057977969 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -246,6 +246,7 @@ def set_tags_subnet(self, subnet_id, tags): """Tag a subnet by passing in one or more tags separated by a comma. :param int subnet_id: The ID of the subnet. + :param string tags: Comma separated list of tags. """ return self.subnet.setTags(tags, id=subnet_id) @@ -253,6 +254,7 @@ def edit_note_subnet(self, subnet_id, note): """Edit the note for this subnet. :param int subnet_id: The ID of the subnet. + :param string note: The note. """ return self.subnet.editNote(note, id=subnet_id) From b168484bac6c56288143d6c1cfbd09ec003a0667 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 15:50:50 -0500 Subject: [PATCH 0377/1582] Update SoftLayer_Network_Subnet.py --- 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 fd7973b49..24683da15 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -33,7 +33,7 @@ 'tag': {'id': 100123, 'name': 'subnet: test tag'}, } - ] + ], 'ipAddresses': [ {'id': 123456, 'ipAddress': '16.26.26.25'}, From 0f3803087b424ab4d5af81d9f6cfa89cd08d4151 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 16:14:05 -0500 Subject: [PATCH 0378/1582] Update subnet_tests.py --- tests/CLI/modules/subnet_tests.py | 34 +++---------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 486292abc..d31bede4e 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -17,38 +17,10 @@ class SubnetTests(testing.TestCase): def test_detail(self): result = self.run_command(['subnet', 'detail', '1234']) - + subnet = json.loads(result.output) self.assert_no_fail(result) - self.assertEqual( - { - 'id': 1234, - 'identifier': '1.2.3.4/26', - 'subnet type': 'ADDITIONAL_PRIMARY', - 'network space': 'PUBLIC', - 'gateway': '1.2.3.254', - 'broadcast': '1.2.3.255', - 'datacenter': 'dal10', - 'vs': [ - { - 'hostname': 'hostname0', - 'domain': 'sl.test', - 'public_ip': '1.2.3.10', - 'private_ip': '10.0.1.2' - } - ], - 'hardware': None, - 'usable ips': 22, - 'note': 'test note', - 'tags': [ - 'subnet: test tag' - ], - 'ipAddresses': { - '123456': '16.26.26.25', - '123457': '16.26.26.26'}, - 'hardware': 'none', - 'usable ips': 22 - }, - json.loads(result.output)) + self.assertEqual(subnet.get('id'), 1234) + self.assertEqual(subnet.get('identifier'),'1.2.3.4/26') def test_list(self): result = self.run_command(['subnet', 'list']) From 8ea87f7b53cb1fbeac9404e2159d9537ca7b9a2c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Jun 2020 16:17:16 -0500 Subject: [PATCH 0379/1582] Update subnet_tests.py --- tests/CLI/modules/subnet_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index d31bede4e..52de2cd27 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -20,7 +20,7 @@ def test_detail(self): subnet = json.loads(result.output) self.assert_no_fail(result) self.assertEqual(subnet.get('id'), 1234) - self.assertEqual(subnet.get('identifier'),'1.2.3.4/26') + self.assertEqual(subnet.get('identifier'), '1.2.3.4/26') def test_list(self): result = self.run_command(['subnet', 'list']) From fe4a2f6fb1b5818a10e59103fe3347afc2e9b0a6 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 30 Jun 2020 12:52:29 -0400 Subject: [PATCH 0380/1582] Add functionality to order a hw for capacityRestrictionType PROCCESOR. --- SoftLayer/managers/ordering.py | 2 +- tests/managers/ordering_tests.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index b5e659ee1..3123d1b02 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -396,7 +396,7 @@ def get_item_price_id(core, prices): capacity_min = int(price.get('capacityRestrictionMinimum', -1)) capacity_max = int(price.get('capacityRestrictionMaximum', -1)) # return first match if no restirction, or no core to check - if capacity_min == -1 or core is None: + if capacity_min == -1 or core is None or "PROCESSOR" in price.get("capacityRestrictionType"): price_id = price['id'] # this check is mostly to work nicely with preset configs elif capacity_min <= int(core) <= capacity_max: diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 39caa72f4..0709eea7e 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -666,6 +666,16 @@ def test_get_item_price_id_storage_with_capacity_restriction(self): self.assertEqual(1234, price_id) + def test_get_item_price_id_processor_with_capacity_restriction(self): + category1 = {'categoryCode': 'cat1'} + price1 = [{'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "1", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "PROCESSOR", + 'categories': [category1]}] + + price_id = self.ordering.get_item_price_id("8", price1) + + self.assertEqual(1234, price_id) + def test_issues1067(self): # https://github.com/softlayer/softlayer-python/issues/1067 item_mock = self.set_mock('SoftLayer_Product_Package', 'getItems') From aa8fd3d14d824edc562666bac49f124938f864c9 Mon Sep 17 00:00:00 2001 From: Jonathan Woodlief Date: Wed, 1 Jul 2020 16:20:18 -0400 Subject: [PATCH 0381/1582] Removed overly specific reference to Block storage I found a specific reference to block storage in code shared between file and block. reworded it to refer to either block or storage volumes --- SoftLayer/managers/storage.py | 4 ++-- tests/managers/block_tests.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 9a8015d58..fb5190d85 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -396,7 +396,7 @@ def failback_from_replicant(self, volume_id): 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 block storage volume. + """Cancels the given storage volume. :param integer volume_id: The volume ID :param string reason: The reason for cancellation @@ -406,7 +406,7 @@ 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("Block Storage was already cancelled") + raise exceptions.SoftLayerError("Storage Volume was already cancelled") billing_item_id = volume['billingItem']['id'] diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index c9731a04d..1ba644236 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -61,7 +61,7 @@ def test_cancel_block_volume_exception_billing_item_not_found(self): immediate=True ) self.assertEqual( - 'Block Storage was already cancelled', + 'Storage Volume was already cancelled', str(exception) ) From 88203f6e00bbd165e5f98ca118bebf703169875e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 2 Jul 2020 18:38:15 -0400 Subject: [PATCH 0382/1582] Refactor functionality to order a hw for capacityRestrictionType PROCCESOR. --- SoftLayer/managers/ordering.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 3123d1b02..bf0d09bbf 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -393,16 +393,23 @@ def get_item_price_id(core, prices): price_id = None for price in prices: if not price['locationGroupId']: - capacity_min = int(price.get('capacityRestrictionMinimum', -1)) - capacity_max = int(price.get('capacityRestrictionMaximum', -1)) - # return first match if no restirction, or no core to check - if capacity_min == -1 or core is None or "PROCESSOR" in price.get("capacityRestrictionType"): - price_id = price['id'] - # this check is mostly to work nicely with preset configs - elif capacity_min <= int(core) <= capacity_max: - if "STORAGE" in price.get("capacityRestrictionType") or "CORE" in price.get( - "capacityRestrictionType"): + restriction = price.get('capacityRestrictionType', False) + # There is a price restriction. Make sure the price is within the restriction + if restriction and core is not None: + capacity_min = int(price.get('capacityRestrictionMinimum', -1)) + capacity_max = int(price.get('capacityRestrictionMaximum', -1)) + if "STORAGE" in restriction: + if capacity_min <= int(core) <= capacity_max: + price_id = price['id'] + if "CORE" in restriction: + if capacity_min <= int(core) <= capacity_max: + price_id = price['id'] + if "PROCESSOR" in restriction: price_id = price['id'] + # No price restrictions + else: + price_id = price['id'] + return price_id def get_item_capacity(self, items, item_keynames): From 02a70614d27de24ca12a31f4d58e5c2d8f4d4142 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 3 Jul 2020 10:24:51 -0400 Subject: [PATCH 0383/1582] add system operation referenceCode in create-option --- SoftLayer/CLI/hardware/create_options.py | 4 +-- SoftLayer/managers/hardware.py | 3 +- tests/CLI/modules/server_tests.py | 39 ++++++++++++------------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 8c005de54..4eba88c84 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -33,10 +33,10 @@ def cli(env): tables.append(preset_table) # Operating systems - os_table = formatting.Table(['operating_system', 'value']) + os_table = formatting.Table(['operating_system', 'value', 'operatingSystemReferenceCode ']) os_table.sortby = 'value' for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key']]) + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) # Port speed diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ec9981c48..b531910e4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -403,7 +403,8 @@ def get_create_options(self): if item['itemCategory']['categoryCode'] == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], - 'key': item['keyName'] + 'key': item['keyName'], + 'referenceCode': item['softwareDescription']['referenceCode'] }) # Port speeds diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index a7fd4e908..605bd32bf 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -362,7 +362,8 @@ def test_create_options(self): {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT'}], + 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'operatingSystemReferenceCode ': 'UBUNTU_14_64'}], [{'port_speed': '10 Mbps Public & Private Network Uplinks', 'value': '10'}], [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] @@ -694,19 +695,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']) @@ -749,12 +750,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) From 24456e9280270be0c9f6f540a6382ef91043e2f3 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 3 Jul 2020 11:02:23 -0400 Subject: [PATCH 0384/1582] fix tox tool --- tests/managers/hardware_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 69e7ae52a..f6a2445d1 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -121,7 +121,8 @@ def test_get_create_options(self): 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'name': 'Ubuntu / 14.04-64'}], + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64'}], 'port_speeds': [{ 'key': '10', 'name': '10 Mbps Public & Private Network Uplinks' @@ -374,10 +375,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From b5ac831a8edd54a0c7128e47abda21afdd23d622 Mon Sep 17 00:00:00 2001 From: allmightyspiff Date: Mon, 6 Jul 2020 14:49:50 -0500 Subject: [PATCH 0385/1582] v5.8.9 release notes --- CHANGELOG.md | 17 +++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626c66af6..2b6b28c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Change Log +## [5.8.9] - 2020-07-06 +https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 + +- #1252 Automated Snap publisher +- #1230 Tag Management + + slcli tags cleanup + + slcli tags delete + + slcli tags details + + slcli tags list + + slcli tags set + + slcli tags taggable +- #1285 Vlan editing functionality +- #1287 Edit IP note and add ipAddress table in detail view +- #1283 Subnet Tagging +- #1291 Storage documentation updates +- #1293 add system operation referenceCode in create-option + ## [5.8.8] - 2020-05-18 https://github.com/softlayer/softlayer-python/compare/v5.8.7...v5.8.8 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 0ea903bf5..cdcdf07ed 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.8' +VERSION = 'v5.8.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 6a6c9a551..8b8308901 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.8', + version='5.8.9', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 83266f6f9645eb2f9f9bf81dfbcbb117526e463d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 8 Jul 2020 17:22:26 -0500 Subject: [PATCH 0386/1582] #828 refactored hardware create to use the ordering manager primarily instead of doing price lookups on its own. Added option to specify a particular networking keyname instead of just a speed --- SoftLayer/CLI/formatting.py | 9 +- SoftLayer/CLI/hardware/create.py | 67 ++---- SoftLayer/CLI/hardware/create_options.py | 28 ++- SoftLayer/managers/hardware.py | 211 +++++++---------- tests/CLI/modules/server_tests.py | 32 +-- tests/managers/hardware_tests.py | 289 +++++------------------ 6 files changed, 193 insertions(+), 443 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b88fe056b..d02ceb1a1 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -301,8 +301,13 @@ def prettytable(self): else: msg = "Column (%s) doesn't exist to sort by" % self.sortby raise exceptions.CLIAbort(msg) - for a_col, alignment in self.align.items(): - table.align[a_col] = alignment + + if isinstance(self.align, str): + table.align = self.align + else: + # Required because PrettyTable has a strict setter function for alignment + for a_col, alignment in self.align.items(): + table.align[a_col] = alignment if self.title: table.title = self.title diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 472cec2e2..eb83e6664 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -12,56 +12,40 @@ @click.command(epilog="See 'slcli server create-options' for valid options.") -@click.option('--hostname', '-H', - help="Host portion of the FQDN", - required=True, - prompt=True) -@click.option('--domain', '-D', - help="Domain portion of the FQDN", - required=True, - prompt=True) -@click.option('--size', '-s', - help="Hardware size", - required=True, - prompt=True) -@click.option('--os', '-o', help="OS install code", - required=True, - prompt=True) -@click.option('--datacenter', '-d', help="Datacenter shortname", - required=True, - prompt=True) -@click.option('--port-speed', - type=click.INT, - help="Port speeds", - required=True, - prompt=True) -@click.option('--billing', +@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('--size', '-s', required=True, prompt=True, + help="Hardware size") +@click.option('--os', '-o', required=True, prompt=True, + help="OS Key value") +@click.option('--datacenter', '-d', required=True, prompt=True, + help="Datacenter shortname") +@click.option('--port-speed', type=click.INT, required=True, prompt=True, + help="Port speeds. DEPRECATED, use --network") +@click.option('--no-public', is_flag=True, + help="Private network only. DEPRECATED, use --network.") +@click.option('--network', + help="Network Option Key.") +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), - default='hourly', - show_default=True, help="Billing rate") -@click.option('--postinstall', '-i', help="Post-install script to download") -@helpers.multi_option('--key', '-k', - help="SSH keys to add to the root user") -@click.option('--no-public', - is_flag=True, - help="Private network only") -@helpers.multi_option('--extra', '-e', help="Extra options") -@click.option('--test', - is_flag=True, +@click.option('--postinstall', '-i', + help="Post-install script. Should be a HTTPS URL.") +@click.option('--test', is_flag=True, help="Do not actually create the server") -@click.option('--template', '-t', - is_eager=True, +@click.option('--template', '-t', is_eager=True, callback=template.TemplateCallback(list_args=['key']), help="A template file that defaults the command-line options", type=click.Path(exists=True, readable=True, resolve_path=True)) -@click.option('--export', - type=click.Path(writable=True, resolve_path=True), +@click.option('--export', type=click.Path(writable=True, resolve_path=True), help="Exports options to a template file") -@click.option('--wait', - type=click.INT, +@click.option('--wait', type=click.INT, help="Wait until the server is finished provisioning for up to " "X seconds before returning") +@helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env def cli(env, **args): """Order/create a dedicated server.""" @@ -86,6 +70,7 @@ def cli(env, **args): 'port_speed': args.get('port_speed'), 'no_public': args.get('no_public') or False, 'extras': args.get('extra'), + 'network': args.get('network') } # Do not create hardware server with --test or --export diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 4eba88c84..2e9949d09 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -19,36 +19,42 @@ def cli(env): tables = [] # Datacenters - dc_table = formatting.Table(['datacenter', 'value']) - dc_table.sortby = 'value' + dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") + dc_table.sortby = 'Value' + dc_table.align = 'l' for location in options['locations']: dc_table.add_row([location['name'], location['key']]) tables.append(dc_table) # Presets - preset_table = formatting.Table(['size', 'value']) - preset_table.sortby = 'value' + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' for size in options['sizes']: preset_table.add_row([size['name'], size['key']]) tables.append(preset_table) # Operating systems - os_table = formatting.Table(['operating_system', 'value', 'operatingSystemReferenceCode ']) - os_table.sortby = 'value' + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' for operating_system in options['operating_systems']: os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) # Port speed - port_speed_table = formatting.Table(['port_speed', 'value']) - port_speed_table.sortby = 'value' + port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['key']]) + port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) tables.append(port_speed_table) # Extras - extras_table = formatting.Table(['extras', 'value']) - extras_table.sortby = 'value' + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' + extras_table.align = 'l' for extra in options['extras']: extras_table.add_row([extra['name'], extra['key']]) tables.append(extras_table) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b531910e4..cccf6b29f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -22,7 +22,9 @@ EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', - 'sec_ip_addresses'] + 'sec_ip_addresses', + 'trusted_platform_module', + 'software_guard_extensions'] class HardwareManager(utils.IdentifierMixin, object): @@ -53,6 +55,7 @@ def __init__(self, client, ordering_manager=None): self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + self.package_keyname = 'BARE_METAL_SERVER' if ordering_manager is None: self.ordering_manager = ordering.OrderingManager(client) else: @@ -337,16 +340,14 @@ def place_order(self, **kwargs): :param string os: operating system name :param int port_speed: Port speed in Mbps :param list ssh_keys: list of ssh key ids - :param string post_uri: The URI of the post-install script to run - after reload - :param boolean hourly: True if using hourly pricing (default). - False for monthly. - :param boolean no_public: True if this server should only have private - interfaces + :param string post_uri: The URI of the post-install script to run after reload + :param boolean hourly: True if using hourly pricing (default). False for monthly. + :param boolean no_public: True if this server should only have private interfaces :param list extras: List of extra feature names """ create_options = self._generate_create_dict(**kwargs) - return self.client['Product_Order'].placeOrder(create_options) + return self.ordering_manager.place_order(**create_options) + # return self.client['Product_Order'].placeOrder(create_options) def verify_order(self, **kwargs): """Verifies an order for a piece of hardware. @@ -354,7 +355,7 @@ def verify_order(self, **kwargs): See :func:`place_order` for a list of available options. """ create_options = self._generate_create_dict(**kwargs) - return self.client['Product_Order'].verifyOrder(create_options) + return self.ordering_manager.verify_order(**create_options) def get_cancellation_reasons(self): """Returns a dictionary of valid cancellation reasons. @@ -397,33 +398,27 @@ def get_create_options(self): 'key': preset['keyName'] }) - # Operating systems operating_systems = [] + port_speeds = [] + extras = [] for item in package['items']: - if item['itemCategory']['categoryCode'] == 'os': + category = item['itemCategory']['categoryCode'] + # Operating systems + if category == 'os': operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], 'referenceCode': item['softwareDescription']['referenceCode'] }) - - # Port speeds - port_speeds = [] - for item in package['items']: - if all([item['itemCategory']['categoryCode'] == 'port_speed', - # Hide private options - not _is_private_port_speed_item(item), - # Hide unbonded options - _is_bonded(item)]): + # Port speeds + elif category == 'port_speed': port_speeds.append({ 'name': item['description'], - 'key': item['capacity'], + 'speed': item['capacity'], + 'key': item['keyName'] }) - - # Extras - extras = [] - for item in package['items']: - if item['itemCategory']['categoryCode'] in EXTRA_CATEGORIES: + # Extras + elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], 'key': item['keyName'] @@ -454,9 +449,7 @@ def _get_package(self): accountRestrictedActivePresets, regions[location[location[priceGroups]]] ''' - - package_keyname = 'BARE_METAL_SERVER' - package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) return package def _generate_create_dict(self, @@ -470,59 +463,66 @@ def _generate_create_dict(self, post_uri=None, hourly=True, no_public=False, - extras=None): + extras=None, + network=None): """Translates arguments into a dictionary for creating a server.""" extras = extras or [] package = self._get_package() - location = _get_location(package, location) - - prices = [] - for category in ['pri_ip_addresses', - 'vpn_management', - 'remote_management']: - prices.append(_get_default_price_id(package['items'], - option=category, - hourly=hourly, - location=location)) - - prices.append(_get_os_price_id(package['items'], os, - location=location)) - prices.append(_get_bandwidth_price_id(package['items'], - hourly=hourly, - no_public=no_public, - location=location)) - prices.append(_get_port_speed_price_id(package['items'], - port_speed, - no_public, - location=location)) + items = package.get('items', {}) + location_id = _get_location(package, location) + + key_names = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP' + ] + + # Operating System + key_names.append(os) + + # Bandwidth Options + key_names.append( + _get_bandwidth_key(items, hourly=hourly, no_public=no_public, location=location_id) + ) + + # Port Speed Options + # New option in v5.9.0 + if network: + key_names.append(network) + # Legacy Option, doesn't support bonded/redundant + else: + key_names.append( + _get_port_speed_key(items, port_speed, no_public, location=location_id) + ) + # Extras for extra in extras: - prices.append(_get_extra_price_id(package['items'], - extra, hourly, - location=location)) + key_names.append(extra) - hardware = { - 'hostname': hostname, - 'domain': domain, + extras = { + 'hardware': [{ + 'hostname': hostname, + 'domain': domain, + }] } - - order = { - 'hardware': [hardware], - 'location': location['keyname'], - 'prices': [{'id': price} for price in prices], - 'packageId': package['id'], - 'presetId': _get_preset_id(package, size), - 'useHourlyPricing': hourly, - } - if post_uri: - order['provisionScripts'] = [post_uri] + extras['provisionScripts'] = [post_uri] if ssh_keys: - order['sshKeys'] = [{'sshKeyIds': ssh_keys}] + extras['sshKeys'] = [{'sshKeyIds': ssh_keys}] + order = { + 'package_keyname': self.package_keyname, + 'location': location, + 'item_keynames': key_names, + 'complex_type': 'SoftLayer_Container_Product_Order_Hardware_Server', + 'hourly': hourly, + 'preset_keyname': size, + 'extras': extras, + 'quantity': 1, + } return order def _get_ids_from_hostname(self, hostname): @@ -745,78 +745,38 @@ def _get_extra_price_id(items, key_name, hourly, location): return price['id'] - raise SoftLayerError( - "Could not find valid price for extra option, '%s'" % key_name) - + raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) -def _get_default_price_id(items, option, hourly, location): - """Returns a 'free' price id given an option.""" - for item in items: - if utils.lookup(item, 'itemCategory', 'categoryCode') != option: - continue - - for price in item['prices']: - if all([float(price.get('hourlyRecurringFee', 0)) == 0.0, - float(price.get('recurringFee', 0)) == 0.0, - _matches_billing(price, hourly), - _matches_location(price, location)]): - return price['id'] - - raise SoftLayerError( - "Could not find valid price for '%s' option" % option) - - -def _get_bandwidth_price_id(items, - hourly=True, - no_public=False, - location=None): - """Choose a valid price id for bandwidth.""" +def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): + """Picks a valid Bandwidth Item, returns the KeyName""" + keyName = None # Prefer pay-for-use data transfer with hourly for item in items: capacity = float(item.get('capacity', 0)) # Hourly and private only do pay-as-you-go bandwidth - if any([utils.lookup(item, - 'itemCategory', - 'categoryCode') != 'bandwidth', + if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'bandwidth', (hourly or no_public) and capacity != 0.0, not (hourly or no_public) and capacity == 0.0]): continue + keyName = item['keyName'] for price in item['prices']: if not _matches_billing(price, hourly): continue if not _matches_location(price, location): continue + return keyName - return price['id'] - - raise SoftLayerError( - "Could not find valid price for bandwidth option") - - -def _get_os_price_id(items, os, location): - """Returns the price id matching.""" - - for item in items: - if any([utils.lookup(item, 'itemCategory', 'categoryCode') != 'os', - utils.lookup(item, 'keyName') != os]): - continue - - for price in item['prices']: - if not _matches_location(price, location): - continue - - return price['id'] + raise SoftLayerError("Could not find valid price for bandwidth option") - raise SoftLayerError("Could not find valid price for os: '%s'" % os) - -def _get_port_speed_price_id(items, port_speed, no_public, location): +def _get_port_speed_key(items, port_speed, no_public, location): """Choose a valid price id for port speed.""" + keyName = None for item in items: if utils.lookup(item, 'itemCategory', 'categoryCode') != 'port_speed': continue @@ -826,12 +786,12 @@ def _get_port_speed_price_id(items, port_speed, no_public, location): _is_private_port_speed_item(item) != no_public, not _is_bonded(item)]): continue - + keyName = item['keyName'] for price in item['prices']: if not _matches_location(price, location): continue - return price['id'] + return keyName raise SoftLayerError( "Could not find valid price for port speed: '%s'" % port_speed) @@ -883,12 +843,3 @@ def _get_location(package, location): return region raise SoftLayerError("Could not find valid location for: '%s'" % location) - - -def _get_preset_id(package, size): - """Get the preset id given the keyName of the preset.""" - for preset in package['activePresets'] + package['accountRestrictedActivePresets']: - if preset['keyName'] == size or preset['id'] == size: - return preset['id'] - - raise SoftLayerError("Could not find valid size for: '%s'" % size) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 605bd32bf..d016c7730 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -355,19 +355,10 @@ def test_create_options(self): result = self.run_command(['server', 'create-options']) self.assert_no_fail(result) - expected = [ - [{'datacenter': 'Washington 1', 'value': 'wdc01'}], - [{'size': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'value': 'S1270_8GB_2X1TBSATA_NORAID'}, - {'size': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10', - 'value': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10'}], - [{'operating_system': 'Ubuntu / 14.04-64', - 'value': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'operatingSystemReferenceCode ': 'UBUNTU_14_64'}], - [{'port_speed': '10 Mbps Public & Private Network Uplinks', - 'value': '10'}], - [{'extras': '1 IPv6 Address', 'value': '1_IPV6_ADDRESS'}]] - self.assertEqual(json.loads(result.output), expected) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Value'], 'wdc01') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): @@ -391,21 +382,6 @@ def test_create_server(self, order_mock): self.assertEqual(json.loads(result.output), {'id': 98765, 'created': '2013-08-02 15:23:47'}) - def test_create_server_missing_required(self): - - # This is missing a required argument - result = self.run_command(['server', 'create', - # Note: no chassis id - '--hostname=test', - '--domain=example.com', - '--datacenter=TEST00', - '--network=100', - '--os=UBUNTU_12_64_MINIMAL', - ]) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, SystemExit) - @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): if (sys.platform.startswith("win")): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index f6a2445d1..2e3d7b3a5 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -7,6 +7,7 @@ import copy import mock +from pprint import pprint as pp import SoftLayer from SoftLayer import fixtures @@ -117,29 +118,29 @@ def test_reload(self): def test_get_create_options(self): options = self.hardware.get_create_options() - expected = { - 'extras': [{'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'}], - 'locations': [{'key': 'wdc01', 'name': 'Washington 1'}], - 'operating_systems': [{'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'name': 'Ubuntu / 14.04-64', - 'referenceCode': 'UBUNTU_14_64'}], - 'port_speeds': [{ - 'key': '10', - 'name': '10 Mbps Public & Private Network Uplinks' - }], - 'sizes': [ - { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' - }, - { - 'key': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10', - 'name': 'Dual Xeon Gold, 384GB Ram, 4x960GB SSD, RAID 10' - } - ] + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + locations = {'key': 'wdc01', 'name': 'Washington 1'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64' + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks' + } + sizes = { + 'key': 'S1270_8GB_2X1TBSATA_NORAID', + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' } - self.assertEqual(options, expected) + self.assertEqual(options['extras'][0], extras) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0], operating_systems) + self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) + self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') @@ -148,18 +149,6 @@ def test_get_create_options_package_missing(self): ex = self.assertRaises(SoftLayer.SoftLayerError, self.hardware.get_create_options) self.assertEqual("Package BARE_METAL_SERVER does not exist", str(ex)) - def test_generate_create_dict_no_items(self): - packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - packages_copy = copy.deepcopy( - fixtures.SoftLayer_Product_Package.getAllObjects) - packages_copy[0]['items'] = [] - packages.return_value = packages_copy - - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware._generate_create_dict, - location="wdc01") - self.assertIn("Could not find valid price", str(ex)) - def test_generate_create_dict_no_regions(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages_copy = copy.deepcopy( @@ -172,20 +161,6 @@ def test_generate_create_dict_no_regions(self): **MINIMAL_TEST_CREATE_ARGS) self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) - def test_generate_create_dict_invalid_size(self): - args = { - 'size': 'UNKNOWN_SIZE', - 'hostname': 'unicorn', - 'domain': 'giggles.woo', - 'location': 'wdc01', - 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'port_speed': 10, - } - - ex = self.assertRaises(SoftLayer.SoftLayerError, - self.hardware._generate_create_dict, **args) - self.assertIn("Could not find valid size for: 'UNKNOWN_SIZE'", str(ex)) - def test_generate_create_dict(self): args = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', @@ -199,51 +174,59 @@ def test_generate_create_dict(self): 'post_uri': 'http://example.com/script.php', 'ssh_keys': [10], } - - expected = { + + package = 'BARE_METAL_SERVER' + location = 'wdc01' + item_keynames = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP', + 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'BANDWIDTH_0_GB_2', + '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + '1_IPV6_ADDRESS' + ] + hourly = True + preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' + extras = { 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', }], - 'location': 'WASHINGTON_DC', - 'packageId': 200, - 'presetId': 64, - 'prices': [{'id': 21}, - {'id': 420}, - {'id': 906}, - {'id': 37650}, - {'id': 1800}, - {'id': 272}, - {'id': 17129}], - 'useHourlyPricing': True, 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys': [{'sshKeyIds': [10]}], + 'sshKeys' : [{'sshKeyIds': [10]}] } data = self.hardware._generate_create_dict(**args) - self.assertEqual(expected, data) + self.assertEqual(package, data['package_keyname']) + self.assertEqual(location, data['location']) + for keyname in item_keynames: + self.assertIn(keyname, data['item_keynames']) + self.assertEqual(extras, data['extras']) - @mock.patch('SoftLayer.managers.hardware.HardwareManager' - '._generate_create_dict') - def test_verify_order(self, create_dict): + + @mock.patch('SoftLayer.managers.ordering.OrderingManager.verify_order') + @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') + def test_verify_order(self, create_dict, verify_order): create_dict.return_value = {'test': 1, 'verify': 1} + verify_order.return_value = {'test': 2} - self.hardware.verify_order(test=1, verify=1) + result = self.hardware.verify_order(test=1, verify=1) + self.assertEqual(2, result['test']) create_dict.assert_called_once_with(test=1, verify=1) - self.assert_called_with('SoftLayer_Product_Order', 'verifyOrder', - args=({'test': 1, 'verify': 1},)) + verify_order.assert_called_once_with(test=1, verify=1) - @mock.patch('SoftLayer.managers.hardware.HardwareManager' - '._generate_create_dict') - def test_place_order(self, create_dict): + @mock.patch('SoftLayer.managers.ordering.OrderingManager.place_order') + @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') + def test_place_order(self, create_dict, place_order): create_dict.return_value = {'test': 1, 'verify': 1} - self.hardware.place_order(test=1, verify=1) - + place_order.return_value = {'test': 1} + result = self.hardware.place_order(test=1, verify=1) + self.assertEqual(1, result['test']) create_dict.assert_called_once_with(test=1, verify=1) - self.assert_called_with('SoftLayer_Product_Order', 'placeOrder', - args=({'test': 1, 'verify': 1},)) + place_order.assert_called_once_with(test=1, verify=1) def test_cancel_hardware_without_reason(self): mock = self.set_mock('SoftLayer_Hardware_Server', 'getObject') @@ -629,162 +612,6 @@ def test_get_hard_drive_empty(self): class HardwareHelperTests(testing.TestCase): - def test_get_extra_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_extra_price_id, - [], 'test', True, None) - self.assertEqual("Could not find valid price for extra option, 'test'", str(ex)) - - def test_get_extra_price_mismatched(self): - items = [ - {'keyName': 'TEST', 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}]}, - {'keyName': 'TEST', 'prices': [{'id': 2, 'locationGroupId': 55, 'hourlyRecurringFee': 99}]}, - {'keyName': 'TEST', 'prices': [{'id': 3, 'locationGroupId': None, 'hourlyRecurringFee': 99}]}, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_extra_price_id(items, 'TEST', True, location) - self.assertEqual(3, result) - - def test_get_bandwidth_price_mismatched(self): - items = [ - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 1, 'locationGroupId': None, 'hourlyRecurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'bandwidth'}, - 'capacity': 100, - 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_bandwidth_price_id(items, False, False, location) - self.assertEqual(3, result) - - def test_get_os_price_mismatched(self): - items = [ - {'itemCategory': {'categoryCode': 'os'}, - 'keyName': 'OS_TEST', - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'os'}, - 'keyName': 'OS_TEST', - 'prices': [{'id': 3, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_os_price_id(items, 'OS_TEST', location) - self.assertEqual(3, result) - - def test_get_default_price_id_item_not_first(self): - items = [{ - 'itemCategory': {'categoryCode': 'unknown', 'id': 325}, - 'keyName': 'UNKNOWN', - 'prices': [{'accountRestrictions': [], - 'currentPriceFlag': '', - 'hourlyRecurringFee': '10.0', - 'id': 1245172, - 'recurringFee': '1.0'}], - }] - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_default_price_id, - items, 'unknown', True, None) - self.assertEqual("Could not find valid price for 'unknown' option", str(ex)) - - def test_get_default_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_default_price_id, - [], 'test', True, None) - self.assertEqual("Could not find valid price for 'test' option", str(ex)) - - def test_get_bandwidth_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_bandwidth_price_id, - [], hourly=True, no_public=False) - self.assertEqual("Could not find valid price for bandwidth option", str(ex)) - - def test_get_os_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_os_price_id, - [], 'UBUNTU_14_64', None) - self.assertEqual("Could not find valid price for os: 'UBUNTU_14_64'", str(ex)) - - def test_get_port_speed_price_id_no_items(self): - ex = self.assertRaises(SoftLayer.SoftLayerError, - managers.hardware._get_port_speed_price_id, - [], 10, True, None) - self.assertEqual("Could not find valid price for port speed: '10'", str(ex)) - - def test_get_port_speed_price_id_mismatch(self): - items = [ - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 101, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 1, 'locationGroupId': None, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_NOT_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 2, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}, {'attributeTypeKeyName': 'NON_LACP'}], - 'prices': [{'id': 3, 'locationGroupId': 55, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 4, 'locationGroupId': 12, 'recurringFee': 99}] - }, - {'itemCategory': {'categoryCode': 'port_speed'}, - 'capacity': 100, - 'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}], - 'prices': [{'id': 5, 'locationGroupId': None, 'recurringFee': 99}] - }, - ] - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._get_port_speed_price_id(items, 100, True, location) - self.assertEqual(5, result) def test_matches_location(self): price = {'id': 1, 'locationGroupId': 51, 'recurringFee': 99} From b6933ebc0365a7ddb559e6989d916d0978fe5832 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 9 Jul 2020 17:26:12 -0500 Subject: [PATCH 0387/1582] #828 full unit test coverage --- SoftLayer/CLI/hardware/create.py | 47 ++++------- SoftLayer/managers/hardware.py | 27 +++--- tests/CLI/modules/server_tests.py | 25 +++++- tests/managers/hardware_tests.py | 132 ++++++++++++++++++++++++++---- 4 files changed, 168 insertions(+), 63 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index eb83e6664..40fa871bc 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -12,38 +12,25 @@ @click.command(epilog="See 'slcli server create-options' for valid options.") -@click.option('--hostname', '-H', required=True, prompt=True, - help="Host portion of the FQDN") -@click.option('--domain', '-D', required=True, prompt=True, - help="Domain portion of the FQDN") -@click.option('--size', '-s', required=True, prompt=True, - help="Hardware size") -@click.option('--os', '-o', required=True, prompt=True, - help="OS Key value") -@click.option('--datacenter', '-d', required=True, prompt=True, - help="Datacenter shortname") -@click.option('--port-speed', type=click.INT, required=True, prompt=True, - help="Port speeds. DEPRECATED, use --network") -@click.option('--no-public', is_flag=True, - help="Private network only. DEPRECATED, use --network.") -@click.option('--network', - help="Network Option Key.") -@click.option('--billing', default='hourly', show_default=True, - type=click.Choice(['hourly', 'monthly']), +@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('--size', '-s', required=True, prompt=True, help="Hardware size") +@click.option('--os', '-o', required=True, prompt=True, help="OS Key value") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--port-speed', type=click.INT, help="Port speeds. DEPRECATED, use --network") +@click.option('--no-public', is_flag=True, help="Private network only. DEPRECATED, use --network.") +@click.option('--network', help="Network Option Key. Use instead of port-speed option") +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), help="Billing rate") -@click.option('--postinstall', '-i', - help="Post-install script. Should be a HTTPS URL.") -@click.option('--test', is_flag=True, - help="Do not actually create the server") -@click.option('--template', '-t', is_eager=True, +@click.option('--postinstall', '-i', help="Post-install script. Should be a HTTPS URL.") +@click.option('--test', is_flag=True, help="Do not actually create the server") +@click.option('--template', '-t', is_eager=True, type=click.Path(exists=True, readable=True, resolve_path=True), callback=template.TemplateCallback(list_args=['key']), - help="A template file that defaults the command-line options", - type=click.Path(exists=True, readable=True, resolve_path=True)) + help="A template file that defaults the command-line options") @click.option('--export', type=click.Path(writable=True, resolve_path=True), help="Exports options to a template file") @click.option('--wait', type=click.INT, - help="Wait until the server is finished provisioning for up to " - "X seconds before returning") + help="Wait until the server is finished provisioning for up to X seconds before returning") @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") @helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env @@ -101,15 +88,13 @@ def cli(env, **args): if args['export']: export_file = args.pop('export') - template.export_to_template(export_file, args, - exclude=['wait', 'test']) + template.export_to_template(export_file, args, exclude=['wait', 'test']) env.fout('Successfully exported options to a template file.') return if do_create: if not (env.skip_confirmations or formatting.confirm( - "This action will incur charges on your account. " - "Continue?")): + "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborting dedicated server order.') result = mgr.place_order(**order) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index cccf6b29f..136b22c0a 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -729,23 +729,23 @@ def get_hard_drives(self, instance_id): return self.hardware.getHardDrives(id=instance_id) -def _get_extra_price_id(items, key_name, hourly, location): - """Returns a price id attached to item with the given key_name.""" +# def _get_extra_price_id(items, key_name, hourly, location): +# """Returns a price id attached to item with the given key_name.""" - for item in items: - if utils.lookup(item, 'keyName') != key_name: - continue +# for item in items: +# if utils.lookup(item, 'keyName') != key_name: +# continue - for price in item['prices']: - if not _matches_billing(price, hourly): - continue +# for price in item['prices']: +# if not _matches_billing(price, hourly): +# continue - if not _matches_location(price, location): - continue +# if not _matches_location(price, location): +# continue - return price['id'] +# return price['id'] - raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) +# raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): @@ -793,8 +793,7 @@ def _get_port_speed_key(items, port_speed, no_public, location): return keyName - raise SoftLayerError( - "Could not find valid price for port speed: '%s'" % port_speed) + raise SoftLayerError("Could not find valid price for port speed: '%s'" % port_speed) def _matches_billing(price, hourly): diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d016c7730..388fc310d 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -359,7 +359,6 @@ def test_create_options(self): self.assertEqual(output[0][0]['Value'], 'wdc01') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { @@ -860,3 +859,27 @@ def test_billing(self): } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) + + def test_create_hw_export(self): + if(sys.platform.startswith("win")): + self.skipTest("Temp files do not work properly in Windows.") + with tempfile.NamedTemporaryFile() as config_file: + result = self.run_command(['hw', 'create', '--hostname=test', '--export', config_file.name, + '--domain=example.com', '--datacenter=TEST00', + '--network=TEST_NETWORK', '--os=UBUNTU_12_64', + '--size=S1270_8GB_2X1TBSATA_NORAID']) + self.assert_no_fail(result) + self.assertTrue('Successfully exported options to a template file.' in result.output) + contents = config_file.read().decode("utf-8") + self.assertIn('hostname=TEST', contents) + self.assertIn('size=S1270_8GB_2X1TBSATA_NORAID', contents) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_hw_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['hw', 'create', '--hostname=test', '--size=S1270_8GB_2X1TBSATA_NORAID', + '--domain=example.com', '--datacenter=TEST00', + '--network=TEST_NETWORK', '--os=UBUNTU_12_64']) + + self.assertEqual(result.exit_code, 2) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 2e3d7b3a5..a61aeece0 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -7,7 +7,6 @@ import copy import mock -from pprint import pprint as pp import SoftLayer from SoftLayer import fixtures @@ -118,7 +117,7 @@ def test_reload(self): def test_get_create_options(self): options = self.hardware.get_create_options() - extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} locations = {'key': 'wdc01', 'name': 'Washington 1'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', @@ -141,7 +140,6 @@ def test_get_create_options(self): self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) self.assertEqual(options['sizes'][0], sizes) - def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages.return_value = [] @@ -174,7 +172,7 @@ def test_generate_create_dict(self): 'post_uri': 'http://example.com/script.php', 'ssh_keys': [10], } - + package = 'BARE_METAL_SERVER' location = 'wdc01' item_keynames = [ @@ -194,7 +192,7 @@ def test_generate_create_dict(self): 'hostname': 'unicorn', }], 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys' : [{'sshKeyIds': [10]}] + 'sshKeys': [{'sshKeyIds': [10]}] } data = self.hardware._generate_create_dict(**args) @@ -204,7 +202,25 @@ def test_generate_create_dict(self): for keyname in item_keynames: self.assertIn(keyname, data['item_keynames']) self.assertEqual(extras, data['extras']) + self.assertEqual(preset_keyname, data['preset_keyname']) + self.assertEqual(hourly, data['hourly']) + def test_generate_create_dict_network_key(self): + args = { + 'size': 'S1270_8GB_2X1TBSATA_NORAID', + 'hostname': 'test1', + 'domain': 'test.com', + 'location': 'wdc01', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'network': 'NETWORKING', + 'hourly': True, + 'extras': ['1_IPV6_ADDRESS'], + 'post_uri': 'http://example.com/script.php', + 'ssh_keys': [10], + } + + data = self.hardware._generate_create_dict(**args) + self.assertIn('NETWORKING', data['item_keynames']) @mock.patch('SoftLayer.managers.ordering.OrderingManager.verify_order') @mock.patch('SoftLayer.managers.hardware.HardwareManager._generate_create_dict') @@ -613,17 +629,99 @@ def test_get_hard_drive_empty(self): class HardwareHelperTests(testing.TestCase): + def set_up(self): + self.items = [ + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 100, + "attributes": [ + {'attributeTypeKeyName': 'NON_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_1", + "prices": [{"id": 1, "locationGroupId": 100}] + }, + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 200, + "attributes": [ + {'attributeTypeKeyName': 'YES_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_2", + "prices": [{"id": 1, "locationGroupId": 151}] + }, + { + "itemCategory": {"categoryCode": "port_speed"}, + "capacity": 200, + "attributes": [ + {'attributeTypeKeyName': 'YES_LACP'}, + {'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'} + ], + "keyName": "ITEM_3", + "prices": [{"id": 1, "locationGroupId": 51}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 0.0, + "attributes": [], + "keyName": "HOURLY_BANDWIDTH_1", + "prices": [{"id": 1, "locationGroupId": 51, "hourlyRecurringFee": 1.0, "recurringFee": 1.0}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 10.0, + "attributes": [], + "keyName": "MONTHLY_BANDWIDTH_1", + "prices": [{"id": 1, "locationGroupId": 151, "recurringFee": 1.0}] + }, + { + "itemCategory": {"categoryCode": "bandwidth"}, + "capacity": 10.0, + "attributes": [], + "keyName": "MONTHLY_BANDWIDTH_2", + "prices": [{"id": 1, "locationGroupId": 51, "recurringFee": 1.0}] + }, + ] + self.location = {'location': {'location': {'priceGroups': [{'id': 50}, {'id': 51}]}}} + + def test_bandwidth_key(self): + result = managers.hardware._get_bandwidth_key(self.items, True, False, self.location) + self.assertEqual('HOURLY_BANDWIDTH_1', result) + result = managers.hardware._get_bandwidth_key(self.items, False, True, self.location) + self.assertEqual('HOURLY_BANDWIDTH_1', result) + result = managers.hardware._get_bandwidth_key(self.items, False, False, self.location) + self.assertEqual('MONTHLY_BANDWIDTH_2', result) + ex = self.assertRaises(SoftLayer.SoftLayerError, + managers.hardware._get_bandwidth_key, [], True, False, self.location) + self.assertEqual("Could not find valid price for bandwidth option", str(ex)) + + def test_port_speed_key(self): + result = managers.hardware._get_port_speed_key(self.items, 200, True, self.location) + self.assertEqual("ITEM_3", result) + + def test_port_speed_key_exception(self): + items = [] + location = {} + ex = self.assertRaises(SoftLayer.SoftLayerError, + managers.hardware._get_port_speed_key, items, 999, False, location) + self.assertEqual("Could not find valid price for port speed: '999'", str(ex)) + def test_matches_location(self): price = {'id': 1, 'locationGroupId': 51, 'recurringFee': 99} - location = { - 'location': { - 'location': { - 'priceGroups': [ - {'id': 50}, - {'id': 51} - ] - } - } - } - result = managers.hardware._matches_location(price, location) - self.assertTrue(result) + + self.assertTrue(managers.hardware._matches_location(price, self.location)) + price['locationGroupId'] = 99999 + self.assertFalse(managers.hardware._matches_location(price, self.location)) + + def test_is_bonded(self): + item_non_lacp = {'attributes': [{'attributeTypeKeyName': 'NON_LACP'}]} + item_lacp = {'attributes': [{'attributeTypeKeyName': 'YES_LACP'}]} + self.assertFalse(managers.hardware._is_bonded(item_non_lacp)) + self.assertTrue(managers.hardware._is_bonded(item_lacp)) + + def test_is_private(self): + item_private = {'attributes': [{'attributeTypeKeyName': 'IS_PRIVATE_NETWORK_ONLY'}]} + item_public = {'attributes': [{'attributeTypeKeyName': 'NOT_PRIVATE_NETWORK_ONLY'}]} + self.assertTrue(managers.hardware._is_private_port_speed_item(item_private)) + self.assertFalse(managers.hardware._is_private_port_speed_item(item_public)) From 6dd7041e24ae4cc15631443ae6dd471680e018c5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 10 Jul 2020 16:26:30 -0500 Subject: [PATCH 0388/1582] #828 code cleanup --- SoftLayer/managers/hardware.py | 19 ------------------- docs/cli/hardware.rst | 2 ++ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 136b22c0a..956d33e3a 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -729,25 +729,6 @@ def get_hard_drives(self, instance_id): return self.hardware.getHardDrives(id=instance_id) -# def _get_extra_price_id(items, key_name, hourly, location): -# """Returns a price id attached to item with the given key_name.""" - -# for item in items: -# if utils.lookup(item, 'keyName') != key_name: -# continue - -# for price in item['prices']: -# if not _matches_billing(price, hourly): -# continue - -# if not _matches_location(price, location): -# continue - -# return price['id'] - -# raise SoftLayerError("Could not find valid price for extra option, '%s'" % key_name) - - def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 0cce23042..5ca325cb7 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -27,6 +27,8 @@ Interacting with Hardware 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. +As of v5.9.0 please use the `--network` option for specifying port speed, as that allows a bit more granularity for choosing your networking type. + .. click:: SoftLayer.CLI.hardware.credentials:cli :prog: hardware credentials :show-nested: From f4c256593ca8066b6f58040c7a542d96751d3ee6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 10 Jul 2020 16:38:11 -0500 Subject: [PATCH 0389/1582] fixed a unit test --- tests/CLI/modules/server_tests.py | 37 +++---------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 388fc310d..f11d9d0a6 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -383,8 +383,7 @@ def test_create_server(self, order_mock): @mock.patch('SoftLayer.CLI.template.export_to_template') def test_create_server_with_export(self, export_mock): - if (sys.platform.startswith("win")): - self.skipTest("Test doesn't work in Windows") + result = self.run_command(['--really', 'server', 'create', '--size=S1270_8GB_2X1TBSATA_NORAID', '--hostname=test', @@ -397,24 +396,8 @@ def test_create_server_with_export(self, export_mock): fmt='raw') self.assert_no_fail(result) - self.assertIn("Successfully exported options to a template file.", - result.output) - export_mock.assert_called_with('/path/to/test_file.txt', - {'billing': 'hourly', - 'datacenter': 'TEST00', - 'domain': 'example.com', - 'extra': (), - 'hostname': 'test', - 'key': (), - 'os': 'UBUNTU_12_64', - 'port_speed': 100, - 'postinstall': None, - 'size': 'S1270_8GB_2X1TBSATA_NORAID', - 'test': False, - 'no_public': True, - 'wait': None, - 'template': None}, - exclude=['wait', 'test']) + self.assertIn("Successfully exported options to a template file.", result.output) + export_mock.assert_called_once() def test_edit_server_userdata_and_file(self): # Test both userdata and userfile at once @@ -860,20 +843,6 @@ def test_billing(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), billing_json) - def test_create_hw_export(self): - if(sys.platform.startswith("win")): - self.skipTest("Temp files do not work properly in Windows.") - with tempfile.NamedTemporaryFile() as config_file: - result = self.run_command(['hw', 'create', '--hostname=test', '--export', config_file.name, - '--domain=example.com', '--datacenter=TEST00', - '--network=TEST_NETWORK', '--os=UBUNTU_12_64', - '--size=S1270_8GB_2X1TBSATA_NORAID']) - self.assert_no_fail(result) - self.assertTrue('Successfully exported options to a template file.' in result.output) - contents = config_file.read().decode("utf-8") - self.assertIn('hostname=TEST', contents) - self.assertIn('size=S1270_8GB_2X1TBSATA_NORAID', contents) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_hw_no_confirm(self, confirm_mock): confirm_mock.return_value = False From d3871cf8aca3f6e92aebed4955344c3948069fdb Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:12:50 -0400 Subject: [PATCH 0390/1582] add user notifications --- SoftLayer/CLI/routes.py | 2 + SoftLayer/CLI/user/edit_notifications.py | 34 ++++++++++++ SoftLayer/CLI/user/notifications.py | 34 ++++++++++++ SoftLayer/managers/user.py | 68 ++++++++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 SoftLayer/CLI/user/edit_notifications.py create mode 100644 SoftLayer/CLI/user/notifications.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 48b0af834..049b43c3b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -316,6 +316,8 @@ ('user:detail', 'SoftLayer.CLI.user.detail:cli'), ('user:permissions', 'SoftLayer.CLI.user.permissions:cli'), ('user:edit-permissions', 'SoftLayer.CLI.user.edit_permissions:cli'), + ('user:notifications', 'SoftLayer.CLI.user.notifications:cli'), + ('user:edit-notifications', 'SoftLayer.CLI.user.edit_notifications:cli'), ('user:edit-details', 'SoftLayer.CLI.user.edit_details:cli'), ('user:create', 'SoftLayer.CLI.user.create:cli'), ('user:delete', 'SoftLayer.CLI.user.delete:cli'), diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py new file mode 100644 index 000000000..b01272b36 --- /dev/null +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -0,0 +1,34 @@ +"""Enable or Disable specific noticication for the current user""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment + + +@click.command() +@click.option('--enable/--disable', default=True, + help="Enable (DEFAULT) or Disable selected notification") +@click.argument('notification', nargs=-1, required=True) +@environment.pass_env +def cli(env, enable, notification): + """Enable or Disable specific notifications. + + Example:: + + slcli user edit-notifications --enable 'Order Approved' 'Reload Complete' + + """ + + mgr = SoftLayer.UserManager(env.client) + + if enable: + result = mgr.enable_notifications(notification) + else: + result = mgr.disable_notifications(notification) + + if result: + click.secho("Notifications updated successfully: %s" % ", ".join(notification), fg='green') + else: + click.secho("Failed to update notifications: %s" % ", ".join(notification), fg='red') diff --git a/SoftLayer/CLI/user/notifications.py b/SoftLayer/CLI/user/notifications.py new file mode 100644 index 000000000..ddb8019e9 --- /dev/null +++ b/SoftLayer/CLI/user/notifications.py @@ -0,0 +1,34 @@ +"""List user notifications""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@environment.pass_env +def cli(env): + """User Notifications.""" + + mgr = SoftLayer.UserManager(env.client) + + all_notifications = mgr.get_all_notifications() + + env.fout(notification_table(all_notifications)) + + +def notification_table(all_notifications): + """Creates a table of available notifications""" + + table = formatting.Table(['Id', 'Name', 'Description', 'Enabled']) + table.align['Id'] = 'l' + table.align['Name'] = 'l' + table.align['Description'] = 'l' + table.align['Enabled'] = 'l' + for notification in all_notifications: + table.add_row([notification['id'], + notification['name'], + notification['description'], + notification['enabled']]) + return table diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 5875d76a8..283208baf 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -36,6 +36,7 @@ def __init__(self, client): self.user_service = self.client['SoftLayer_User_Customer'] self.override_service = self.client['Network_Service_Vpn_Overrides'] self.account_service = self.client['SoftLayer_Account'] + self.subscription_service = self.client['SoftLayer_Email_Subscription'] self.resolvers = [self._get_id_from_username] self.all_permissions = None @@ -85,6 +86,56 @@ def get_all_permissions(self): self.all_permissions = sorted(permissions, key=itemgetter('keyName')) return self.all_permissions + def get_all_notifications(self): + """Calls SoftLayer_Email_Subscription::getAllObjects + + Stores the result in self.all_permissions + :returns: A list of dictionaries that contains all valid permissions + """ + return self.subscription_service.getAllObjects(mask='mask[enabled]') + + def enable_notifications(self, notifications_names): + """Enables a list of notifications for the current a user profile. + + :param list notifications_names: List of notifications names to enable + :returns: True on success + + Example:: + enable_notifications(['Order Approved','Reload Complete']) + """ + + result = False + notifications = self.gather_notifications(notifications_names) + for notification in notifications: + notification_id = notification.get('id') + result = self.subscription_service.enable(id=notification_id) + if result: + continue + else: + return False + return result + + def disable_notifications(self, notifications_names): + """Disable a list of notifications for the current a user profile. + + :param list notifications_names: List of notifications names to disable + :returns: True on success + + Example:: + disable_notifications(['Order Approved','Reload Complete']) + """ + + result = False + notifications = self.gather_notifications(notifications_names) + for notification in notifications: + notification_id = notification.get('id') + result = self.subscription_service.disable(id=notification_id) + if result: + continue + else: + return False + return result + def add_permissions(self, user_id, permissions): """Enables a list of permissions for a user @@ -237,6 +288,23 @@ def format_permission_object(self, permissions): raise exceptions.SoftLayerError("'%s' is not a valid permission" % permission) return pretty_permissions + def gather_notifications(self, notifications_names): + """Gets a list of notifications. + + :param list notifications_names: A list of notifications names. + :returns: list of notifications. + """ + notifications = [] + available_notifications = self.get_all_notifications() + for notification in notifications_names: + result = next((item for item in available_notifications + if item.get('name') == notification), None) + if result: + notifications.append(result) + else: + raise exceptions.SoftLayerError("{} is not a valid notification name".format(notification)) + return notifications + def create_user(self, user_object, password): """Blindly sends user_object to SoftLayer_User_Customer::createObject From 2e4618acc47a33682ee72a430588f771e3357e35 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:13:58 -0400 Subject: [PATCH 0391/1582] add user notifications tests --- .../fixtures/SoftLayer_Email_Subscription.py | 22 +++++++++ tests/CLI/modules/user_tests.py | 32 +++++++++++++ tests/managers/user_tests.py | 48 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 SoftLayer/fixtures/SoftLayer_Email_Subscription.py diff --git a/SoftLayer/fixtures/SoftLayer_Email_Subscription.py b/SoftLayer/fixtures/SoftLayer_Email_Subscription.py new file mode 100644 index 000000000..bc3104b16 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Email_Subscription.py @@ -0,0 +1,22 @@ +getAllObjects = [ + {'description': 'Email about your order.', + 'enabled': True, + 'id': 1, + 'name': 'Order Being Reviewed' + }, + {'description': 'Maintenances that will or are likely to cause service ' + 'outages and disruptions', + 'enabled': True, + 'id': 8, + 'name': 'High Impact' + }, + {'description': 'Testing description.', + 'enabled': True, + 'id': 111, + 'name': 'Test notification' + } +] + +enable = True + +disable = True diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 6f58c14a0..f16ef1843 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -305,3 +305,35 @@ def test_vpn_subnet_remove(self, click): result = self.run_command(['user', 'vpn-subnet', '12345', '--remove', '1234']) click.secho.assert_called_with('12345 updated successfully', fg='green') self.assert_no_fail(result) + + """User notification tests""" + + def test_notificacions_list(self): + result = self.run_command(['user', 'notifications']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'getAllObjects', mask='mask[enabled]') + + """User edit-notification tests""" + + def test_edit_notification_on(self): + result = self.run_command(['user', 'edit-notifications', '--enable', 'Test notification']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + + def test_edit_notification_on_bad(self): + result = self.run_command(['user', 'edit-notifications', '--enable', 'Test not exist']) + self.assertEqual(result.exit_code, 1) + + def test_edit_notifications_off(self): + result = self.run_command(['user', 'edit-notifications', '--disable', 'Test notification']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + @mock.patch('SoftLayer.CLI.user.edit_notifications.click') + def test_edit_notification_off_failure(self, click): + notification = self.set_mock('SoftLayer_Email_Subscription', 'disable') + notification.return_value = False + result = self.run_command(['user', 'edit-notifications', '--disable', 'Test notification']) + click.secho.assert_called_with('Failed to update notifications: Test notification', fg='red') + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index b0ab015f9..61f5b4d0a 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -4,7 +4,9 @@ """ import datetime + import mock + import SoftLayer from SoftLayer import exceptions from SoftLayer import testing @@ -246,3 +248,49 @@ def test_vpn_subnet_remove(self): self.manager.vpn_subnet_remove(user_id, [subnet_id]) self.assert_called_with('SoftLayer_Network_Service_Vpn_Overrides', 'deleteObjects', args=expected_args) self.assert_called_with('SoftLayer_User_Customer', 'updateVpnUser', identifier=user_id) + + def test_get_all_notifications(self): + self.manager.get_all_notifications() + self.assert_called_with('SoftLayer_Email_Subscription', 'getAllObjects') + + def test_enable_notifications(self): + self.manager.enable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + + def test_disable_notifications(self): + self.manager.disable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + + def test_enable_notifications_fail(self): + notification = self.set_mock('SoftLayer_Email_Subscription', 'enable') + notification.return_value = False + result = self.manager.enable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'enable', identifier=111) + self.assertFalse(result) + + def test_disable_notifications_fail(self): + notification = self.set_mock('SoftLayer_Email_Subscription', 'disable') + notification.return_value = False + result = self.manager.disable_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', 'disable', identifier=111) + self.assertFalse(result) + + def test_gather_notifications(self): + expected_result = [ + {'description': 'Testing description.', + 'enabled': True, + 'id': 111, + 'name': 'Test notification' + } + ] + result = self.manager.gather_notifications(['Test notification']) + self.assert_called_with('SoftLayer_Email_Subscription', + 'getAllObjects', + mask='mask[enabled]') + self.assertEqual(result, expected_result) + + def test_gather_notifications_fail(self): + ex = self.assertRaises(SoftLayer.SoftLayerError, + self.manager.gather_notifications, + ['Test not exit']) + self.assertEqual("Test not exit is not a valid notification name", str(ex)) From eb6631697f6de79323c3440eb633169dd542a656 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 10 Jul 2020 22:14:42 -0400 Subject: [PATCH 0392/1582] add user notifications docs --- docs/cli/users.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/cli/users.rst b/docs/cli/users.rst index feb94e352..5195a7788 100644 --- a/docs/cli/users.rst +++ b/docs/cli/users.rst @@ -12,10 +12,18 @@ Version 5.6.0 introduces the ability to interact with user accounts from the cli :prog: user detail :show-nested: +.. click:: SoftLayer.CLI.user.notifications:cli + :prog: user notifications + :show-nested: + .. click:: SoftLayer.CLI.user.permissions:cli :prog: user permissions :show-nested: +.. click:: SoftLayer.CLI.user.edit_notifications:cli + :prog: user edit-notifications + :show-nested: + .. click:: SoftLayer.CLI.user.edit_permissions:cli :prog: user edit-permissions :show-nested: From e423d6b83f99c0d2bd34b7408211eb72c3485b4d Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 13 Jul 2020 15:24:22 -0400 Subject: [PATCH 0393/1582] fix tox analysis --- SoftLayer/managers/user.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 283208baf..0948df8b3 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -109,9 +109,7 @@ def enable_notifications(self, notifications_names): for notification in notifications: notification_id = notification.get('id') result = self.subscription_service.enable(id=notification_id) - if result: - continue - else: + if not result: return False return result @@ -130,9 +128,7 @@ def disable_notifications(self, notifications_names): for notification in notifications: notification_id = notification.get('id') result = self.subscription_service.disable(id=notification_id) - if result: - continue - else: + if not result: return False return result From 744213a800ca827fbd1a790110ebb6063a914bfa Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 14 Jul 2020 15:55:24 -0400 Subject: [PATCH 0394/1582] improve user notifications docs --- SoftLayer/CLI/user/edit_notifications.py | 3 ++- SoftLayer/CLI/user/notifications.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py index b01272b36..acef3380b 100644 --- a/SoftLayer/CLI/user/edit_notifications.py +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -13,7 +13,8 @@ @click.argument('notification', nargs=-1, required=True) @environment.pass_env def cli(env, enable, notification): - """Enable or Disable specific notifications. + """Enable or Disable specific notifications for the active user. + Notification names should be enclosed in quotation marks. Example:: diff --git a/SoftLayer/CLI/user/notifications.py b/SoftLayer/CLI/user/notifications.py index ddb8019e9..deffd951e 100644 --- a/SoftLayer/CLI/user/notifications.py +++ b/SoftLayer/CLI/user/notifications.py @@ -9,7 +9,7 @@ @click.command() @environment.pass_env def cli(env): - """User Notifications.""" + """My Notifications.""" mgr = SoftLayer.UserManager(env.client) From 9c940744471f4a3bc2482a1c1fdfa086b55ee9c4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 14 Jul 2020 16:06:48 -0400 Subject: [PATCH 0395/1582] clean code --- SoftLayer/CLI/user/edit_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/edit_notifications.py b/SoftLayer/CLI/user/edit_notifications.py index acef3380b..1be7527c2 100644 --- a/SoftLayer/CLI/user/edit_notifications.py +++ b/SoftLayer/CLI/user/edit_notifications.py @@ -14,8 +14,8 @@ @environment.pass_env def cli(env, enable, notification): """Enable or Disable specific notifications for the active user. - Notification names should be enclosed in quotation marks. + Notification names should be enclosed in quotation marks. Example:: slcli user edit-notifications --enable 'Order Approved' 'Reload Complete' From 47fb7896c3c004ea1abb7aa669c897e92b5d0163 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 16 Jul 2020 15:10:35 -0500 Subject: [PATCH 0396/1582] #874 added 'vs migrate' command --- SoftLayer/CLI/dedicatedhost/detail.py | 2 +- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/migrate.py | 80 +++++++++++++++++++++++++++ SoftLayer/managers/vs.py | 20 +++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/virt/migrate.py diff --git a/SoftLayer/CLI/dedicatedhost/detail.py b/SoftLayer/CLI/dedicatedhost/detail.py index e1c46b962..ca966d03a 100644 --- a/SoftLayer/CLI/dedicatedhost/detail.py +++ b/SoftLayer/CLI/dedicatedhost/detail.py @@ -19,7 +19,7 @@ @click.option('--guests', is_flag=True, help='Show guests on dedicated host') @environment.pass_env def cli(env, identifier, price=False, guests=False): - """Get details for a virtual server.""" + """Get details for a dedicated host.""" dhost = SoftLayer.DedicatedHostManager(env.client) table = formatting.KeyValueTable(['name', 'value']) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 049b43c3b..424f58957 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -46,6 +46,7 @@ ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), + ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), ('dedicatedhost', 'SoftLayer.CLI.dedicatedhost'), ('dedicatedhost:list', 'SoftLayer.CLI.dedicatedhost.list:cli'), diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py new file mode 100644 index 000000000..9e48fb208 --- /dev/null +++ b/SoftLayer/CLI/virt/migrate.py @@ -0,0 +1,80 @@ +"""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_filer = {'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_filer, 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]") + for vsi_object in require_migration: + migrate(vsi, guest) + # 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/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..d9df28704 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1158,3 +1158,23 @@ def get_local_disks(self, instance_id): """ mask = 'mask[diskImage]' return self.guest.getBlockDevices(mask=mask, id=instance_id) + + def migrate(self, instance_id): + """Calls SoftLayer_Virtual_Guest::migrate + + Only actually does anything if the virtual server requires a migration. + Will return an exception otherwise. + + :param int instance_id: Id of the virtual server + """ + return self.guest.migrate(id=instance_id) + + def migrate_dedicated(self, instance_id, host_id): + """Calls SoftLayer_Virtual_Guest::migrate + + Only actually does anything if the virtual server requires a migration. + Will return an exception otherwise. + + :param int instance_id: Id of the virtual server + """ + return self.guest.migrateDedicatedHost(host_id, id=instance_id) From 8a22d39708cde41729bb84a2c8d4d3555b195e61 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 16 Jul 2020 17:26:24 -0500 Subject: [PATCH 0397/1582] adding unit tests --- SoftLayer/CLI/virt/migrate.py | 2 +- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 3 ++ SoftLayer/testing/__init__.py | 10 +++++ tests/CLI/modules/vs/vs_tests.py | 43 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 9e48fb208..5b97ea46b 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -61,7 +61,7 @@ def cli(env, guest, migrate_all, host): elif migrate_all: require_migration = vsi.list_instances(filter=pending_filter, mask="mask[id]") for vsi_object in require_migration: - migrate(vsi, guest) + migrate(vsi, vsi_object['id']) # Just migrate based on the options else: migrate(vsi, guest, host) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c42963c8e..08742a784 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -767,3 +767,6 @@ } } ] + +migrate = True +migrateDedicatedHost = True \ No newline at end of file diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 9c8b81c47..40a224aac 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -142,6 +142,16 @@ def assert_called_with(self, service, method, **props): raise AssertionError('%s::%s was not called with given properties: %s' % (service, method, props)) + def assert_not_called_with(self, service, method, **props): + """Used to assert that API calls were NOT called with given properties. + + Props are properties of the given transport.Request object. + """ + + if self.calls(service, method, **props): + raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props)) + + def assert_no_fail(self, result): """Fail when a failing click result has an error""" if result.exception: diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..ee743a137 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -815,3 +815,46 @@ def test_billing(self): } self.assert_no_fail(result) self.assertEqual(json.loads(result.output), vir_billing) + + def test_vs_migrate_list(self): + result = self.run_command(['vs', 'migrate']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_guest(self): + result = self.run_command(['vs', 'migrate', '-g', '100']) + + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_all(self): + result = self.run_command(['vs', 'migrate', '-a']) + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + + def test_vs_migrate_dedicated(self): + result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) + self.assert_no_fail(result) + self.assertIn('Started a migration on', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) + + def test_vs_migrate_exception(self): + ex = SoftLayerAPIError('SoftLayer_Exception', 'PROBLEM') + mock = self.set_mock('SoftLayer_Virtual_Guest', 'migrate') + mock.side_effect = ex + result = self.run_command(['vs', 'migrate', '-g', '100']) + self.assert_no_fail(result) + self.assertIn('Failed to migrate', result.output) + self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) From cda861a2fc1ef5420136c9d1606953c6b462bf49 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Jul 2020 18:31:54 -0400 Subject: [PATCH 0398/1582] #1298 refactor get local type disks --- SoftLayer/CLI/virt/detail.py | 13 +------------ SoftLayer/CLI/virt/storage.py | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index d17e489f2..c07ef657c 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -9,6 +9,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer.CLI.virt.storage import get_local_type from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -200,15 +201,3 @@ def _get_security_table(result): return secgroup_table else: return None - - -def get_local_type(disks): - """Returns the virtual server local disk type. - - :param disks: virtual serve local disks. - """ - disk_type = 'System' - if 'SWAP' in disks['diskImage']['description']: - disk_type = 'Swap' - - return disk_type diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 8d1b65854..802ae32d9 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -67,7 +67,7 @@ def get_local_type(disks): :param disks: virtual serve local disks. """ disk_type = 'System' - if 'SWAP' in disks['diskImage']['description']: + if 'SWAP' in disks.get('diskImage', {}).get('description', []): disk_type = 'Swap' return disk_type From 3f8361834f46f17e56d6662922d3e5d466a0d175 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Jul 2020 16:42:01 -0500 Subject: [PATCH 0399/1582] finishing up tests --- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 2 +- SoftLayer/testing/__init__.py | 1 - tests/CLI/modules/vs/vs_tests.py | 11 ++++++++++- tests/managers/vs/vs_tests.py | 10 ++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 08742a784..755c284ff 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -769,4 +769,4 @@ ] migrate = True -migrateDedicatedHost = True \ No newline at end of file +migrateDedicatedHost = True diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 40a224aac..f1404b423 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -151,7 +151,6 @@ def assert_not_called_with(self, service, method, **props): if self.calls(service, method, **props): raise AssertionError('%s::%s was called with given properties: %s' % (service, method, props)) - def assert_no_fail(self, result): """Fail when a failing click result has an error""" if result.exception: diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index ee743a137..cb451fa60 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -823,9 +823,18 @@ def test_vs_migrate_list(self): self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_list_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + mock.return_value = [] + result = self.run_command(['vs', 'migrate']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrate') + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + self.assertIn("No guests require migration at this time", result.output) + def test_vs_migrate_guest(self): result = self.run_command(['vs', 'migrate', '-g', '100']) - self.assert_no_fail(result) self.assertIn('Started a migration on', result.output) self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 40fb3063f..1128e3b0e 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1132,3 +1132,13 @@ def test_get_local_disks_swap(self): } } ], result) + + def test_migrate(self): + result = self.vs.migrate(1234) + self.assertTrue(result) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=1234) + + def test_migrate_dedicated(self): + result = self.vs.migrate_dedicated(1234, 5555) + self.assertTrue(result) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(5555,), identifier=1234) From e331f57803d2bde0fe15d3181d5ec9bad141afeb Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Jul 2020 17:09:23 -0500 Subject: [PATCH 0400/1582] documentation --- docs/cli/vs.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 49b99e09c..09539a72b 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -264,6 +264,11 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual credentials :show-nested: +.. click:: SoftLayer.CLI.virt.migrate:cli + :prog: virtual migrate + :show-nested: + +Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity ----------------- From beace276a551a79cdf3ea1b9130b0dfbd40b116f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 12:42:14 -0400 Subject: [PATCH 0401/1582] List hardware vs associated. Add vs list hw vs associated. --- SoftLayer/CLI/hardware/guests.py | 38 ++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/list.py | 22 ++++++- .../fixtures/SoftLayer_Hardware_Server.py | 20 +++++++ SoftLayer/fixtures/SoftLayer_Virtual_Host.py | 40 +++++++++++++ SoftLayer/managers/hardware.py | 14 ++++- SoftLayer/managers/vs.py | 7 +++ tests/CLI/modules/server_tests.py | 12 ++++ tests/managers/hardware_tests.py | 48 +++++++++++++++ tests/managers/vs/vs_tests.py | 58 +++++++++++++++++++ 10 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 SoftLayer/CLI/hardware/guests.py create mode 100644 SoftLayer/fixtures/SoftLayer_Virtual_Host.py diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py new file mode 100644 index 000000000..0bb6a0501 --- /dev/null +++ b/SoftLayer/CLI/hardware/guests.py @@ -0,0 +1,38 @@ +"""List the Hardware server associated virtual guests.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer import utils +from SoftLayer.CLI import environment, formatting, exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """List the Hardware server associated virtual guests.""" + + mgr = SoftLayer.HardwareManager(env.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') + hw_guests = mgr.get_hardware_guests(hw_id) + + if not hw_guests: + raise exceptions.CLIAbort("The hardware server does not has associated virtual guests.") + + table = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState']) + table.sortby = 'hostname' + for guest in hw_guests: + table.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 049b43c3b..a84de0157 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -236,6 +236,7 @@ ('hardware:detail', 'SoftLayer.CLI.hardware.detail:cli'), ('hardware:billing', 'SoftLayer.CLI.hardware.billing:cli'), ('hardware:edit', 'SoftLayer.CLI.hardware.edit:cli'), + ('hardware:guests', 'SoftLayer.CLI.hardware.guests:cli'), ('hardware:list', 'SoftLayer.CLI.hardware.list:cli'), ('hardware:power-cycle', 'SoftLayer.CLI.hardware.power:power_cycle'), ('hardware:power-off', 'SoftLayer.CLI.hardware.power:power_off'), diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 6bf9e6bb6..3a24ffe9d 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -4,12 +4,12 @@ import click import SoftLayer +from SoftLayer import utils from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers - # pylint: disable=unnecessary-lambda COLUMNS = [ @@ -93,3 +93,23 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, for value in columns.row(guest)]) env.fout(table) + + hardware_guests = vsi.get_hardware_guests() + for hardware in hardware_guests: + if 'virtualHost' in hardware and hardware['virtualHost']['guests']: + table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', + 'powerState'], title="Hardware(id = {hardwareId}) guests " + "associated".format(hardwareId=hardware['id']) + ) + table_hardware_guest.sortby = 'hostname' + for guest in hardware['virtualHost']['guests']: + table_hardware_guest.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + env.fout(table_hardware_guest) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index e90288753..5c26d20da 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -242,3 +242,23 @@ } } ] + +getVirtualHost = { + "accountId": 11111, + "createDate": "2018-10-08T10:54:48-06:00", + "description": "host16.vmware.chechu.com", + "hardwareId": 22222, + "id": 33333, + "name": "host16.vmware.chechu.com", + "uuid": "00000000-0000-0000-0000-0cc11111", + "hardware": { + "accountId": 11111, + "domain": "chechu.com", + "hostname": "host16.vmware", + "id": 22222, + "hardwareStatus": { + "id": 5, + "status": "ACTIVE" + } + } +} diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Host.py b/SoftLayer/fixtures/SoftLayer_Virtual_Host.py new file mode 100644 index 000000000..c5b20a34b --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Host.py @@ -0,0 +1,40 @@ +getGuests = [ + { + "accountId": 11111, + "createDate": "2019-09-05T17:03:42-06:00", + "fullyQualifiedDomainName": "NSX-T Manager", + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "startCpus": 16, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }, + { + "accountId": 11111, + "createDate": "2019-09-23T06:00:53-06:00", + "hostname": "NSX-T Manager2", + "id": 33333, + "maxCpu": 12, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "startCpus": 12, + "statusId": 1001, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 956d33e3a..2214ee8c2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -728,6 +728,18 @@ def get_hard_drives(self, instance_id): """ return self.hardware.getHardDrives(id=instance_id) + def get_hardware_guests(self, instance_id): + """Returns the hardware server guests. + + :param int instance_id: Id of the hardware server. + """ + mask = "mask[id]" + virtual_host = self.hardware.getVirtualHost(mask=mask, id=instance_id) + if virtual_host: + return self.client.call('SoftLayer_Virtual_Host', 'getGuests', mask='mask[powerState]', + id=virtual_host['id']) + return virtual_host + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..9426fb53f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1158,3 +1158,10 @@ def get_local_disks(self, instance_id): """ mask = 'mask[diskImage]' return self.guest.getBlockDevices(mask=mask, id=instance_id) + + def get_hardware_guests(self): + """Returns the hardware virtual server associated. + """ + object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} + mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" + return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f11d9d0a6..4ef1a7354 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -852,3 +852,15 @@ def test_create_hw_no_confirm(self, confirm_mock): '--network=TEST_NETWORK', '--os=UBUNTU_12_64']) self.assertEqual(result.exit_code, 2) + + def test_get_hardware_guests(self): + result = self.run_command(['hw', 'guests', '123456']) + self.assert_no_fail(result) + + def test_hardware_guests_empty(self): + mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') + mock.return_value = None + + result = self.run_command(['hw', 'guests', '123456']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a61aeece0..a7824dd61 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -626,6 +626,54 @@ def test_get_hard_drive_empty(self): self.assertEqual([], result) + def test_get_hardware_guests_empty_virtualHost(self): + mock = self.set_mock('SoftLayer_Hardware_Server', 'getVirtualHost') + mock.return_value = None + + result = self.hardware.get_hardware_guests(1234) + + self.assertEqual(None, result) + + def test_get_hardware_guests(self): + mock = self.set_mock('SoftLayer_Virtual_Host', 'getGuests') + mock.return_value = [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }] + + result = self.hardware.get_hardware_guests(1234) + + self.assertEqual([ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 22222, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }], result) + class HardwareHelperTests(testing.TestCase): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index 40fb3063f..fcee57be5 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1132,3 +1132,61 @@ def test_get_local_disks_swap(self): } } ], result) + + def test_get_hardware_guests(self): + mock = self.set_mock('SoftLayer_Account', 'getHardware') + mock.return_value = [{ + "accountId": 11111, + "domain": "vmware.chechu.com", + "hostname": "host14", + "id": 22222, + "virtualHost": { + "accountId": 11111, + "id": 33333, + "name": "host14.vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 44444, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]}}] + + result = self.vs.get_hardware_guests() + + self.assertEqual([{ + "accountId": 11111, + "domain": "vmware.chechu.com", + "hostname": "host14", + "id": 22222, + "virtualHost": { + "accountId": 11111, + "id": 33333, + "name": "host14.vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "hostname": "NSX-T Manager", + "id": 44444, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]}}], result) From a3d6ef5a29c33ba3e2d537898d7b29363266201a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 12:50:26 -0400 Subject: [PATCH 0402/1582] Add hardware guests documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index 5ca325cb7..f7691e700 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -115,3 +115,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.storage:cli :prog: hardware storage :show-nested: + +.. click:: SoftLayer.CLI.hardware.guests:cli + :prog: hardware guests + :show-nested: From 69c90adbf8db457a3888769662f14af741659748 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 22 Jul 2020 13:12:36 -0400 Subject: [PATCH 0403/1582] Fi tox analysis. --- SoftLayer/CLI/hardware/guests.py | 6 ++++-- SoftLayer/CLI/virt/list.py | 2 +- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/vs.py | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py index 0bb6a0501..418cf9d6e 100644 --- a/SoftLayer/CLI/hardware/guests.py +++ b/SoftLayer/CLI/hardware/guests.py @@ -4,9 +4,11 @@ import click import SoftLayer -from SoftLayer import utils -from SoftLayer.CLI import environment, formatting, exceptions +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils @click.command() diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 3a24ffe9d..925f33d2a 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -4,11 +4,11 @@ import click import SoftLayer -from SoftLayer import utils from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils # pylint: disable=unnecessary-lambda diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 2214ee8c2..1c2a097f1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager +from SoftLayer import utils LOGGER = logging.getLogger(__name__) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 9426fb53f..6c4d67786 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1160,7 +1160,9 @@ def get_local_disks(self, instance_id): return self.guest.getBlockDevices(mask=mask, id=instance_id) def get_hardware_guests(self): - """Returns the hardware virtual server associated. + """Returns the hardware server vs associated. + + :return SoftLayer_Hardware[]. """ object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" From fb59874ea7eee7fb30411d56d80066abdbb44f31 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 22 Jul 2020 15:30:07 -0500 Subject: [PATCH 0404/1582] #875 added option to reload bare metal servers with LVM enabled --- SoftLayer/CLI/hardware/reload.py | 13 ++++++------- SoftLayer/managers/hardware.py | 12 +++++++----- tests/CLI/modules/server_tests.py | 10 +++++++--- tests/managers/hardware_tests.py | 12 +++++++----- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/hardware/reload.py b/SoftLayer/CLI/hardware/reload.py index 11286d0d9..798e01968 100644 --- a/SoftLayer/CLI/hardware/reload.py +++ b/SoftLayer/CLI/hardware/reload.py @@ -13,17 +13,16 @@ @click.command() @click.argument('identifier') @click.option('--postinstall', '-i', - help=("Post-install script to download " - "(Only HTTPS executes, HTTP leaves file in /root")) + help=("Post-install script to download (Only HTTPS executes, HTTP leaves file in /root")) @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") +@click.option('--lvm', '-l', is_flag=True, default=False, show_default=True, + help="A flag indicating that the provision should use LVM for all logical drives.") @environment.pass_env -def cli(env, identifier, postinstall, key): +def cli(env, identifier, postinstall, key, lvm): """Reload operating system on a server.""" hardware = SoftLayer.HardwareManager(env.client) - hardware_id = helpers.resolve_id(hardware.resolve_ids, - identifier, - 'hardware') + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') key_list = [] if key: for single_key in key: @@ -33,4 +32,4 @@ def cli(env, identifier, postinstall, key): if not (env.skip_confirmations or formatting.no_going_back(hardware_id)): raise exceptions.CLIAbort('Aborted') - hardware.reload(hardware_id, postinstall, key_list) + hardware.reload(hardware_id, postinstall, key_list, lvm) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 956d33e3a..2db54ed25 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -269,13 +269,14 @@ def get_hardware(self, hardware_id, **kwargs): return self.hardware.getObject(id=hardware_id, **kwargs) - def reload(self, hardware_id, post_uri=None, ssh_keys=None): + def reload(self, hardware_id, post_uri=None, ssh_keys=None, lvm=False): """Perform an OS reload of a server with its current configuration. + https://sldn.softlayer.com/reference/datatypes/SoftLayer_Container_Hardware_Server_Configuration/ :param integer hardware_id: the instance ID to reload - :param string post_uri: The URI of the post-install script to run - after reload + :param string post_uri: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user + :param bool lvm: A flag indicating that the provision should use LVM for all logical drives. """ config = {} @@ -285,9 +286,10 @@ def reload(self, hardware_id, post_uri=None, ssh_keys=None): if ssh_keys: config['sshKeyIds'] = list(ssh_keys) + if lvm: + config['lvmFlag'] = lvm - return self.hardware.reloadOperatingSystem('FORCE', config, - id=hardware_id) + return self.hardware.reloadOperatingSystem('FORCE', config, id=hardware_id) def rescue(self, hardware_id): """Reboot a server into the a recsue kernel. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f11d9d0a6..1f539c84e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -219,11 +219,15 @@ def test_server_reload(self, reload_mock, ngb_mock): ngb_mock.return_value = False # Check the positive case - result = self.run_command(['--really', 'server', 'reload', '12345', - '--key=4567']) + result = self.run_command(['--really', 'server', 'reload', '12345', '--key=4567']) self.assert_no_fail(result) - reload_mock.assert_called_with(12345, None, [4567]) + reload_mock.assert_called_with(12345, None, [4567], False) + + # LVM switch + result = self.run_command(['--really', 'server', 'reload', '12345', '--lvm']) + self.assert_no_fail(result) + reload_mock.assert_called_with(12345, None, [], True) # Now check to make sure we properly call CLIAbort in the negative case result = self.run_command(['server', 'reload', '12345']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a61aeece0..d7516aec0 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -107,13 +107,15 @@ def test_reload(self): result = self.hardware.reload(1, post_uri=post_uri, ssh_keys=[1701]) self.assertEqual(result, 'OK') - self.assert_called_with('SoftLayer_Hardware_Server', - 'reloadOperatingSystem', - args=('FORCE', - {'customProvisionScriptUri': post_uri, - 'sshKeyIds': [1701]}), + self.assert_called_with('SoftLayer_Hardware_Server', 'reloadOperatingSystem', + args=('FORCE', {'customProvisionScriptUri': post_uri, 'sshKeyIds': [1701]}), identifier=1) + result = self.hardware.reload(100, lvm=True) + self.assertEqual(result, 'OK') + self.assert_called_with('SoftLayer_Hardware_Server', 'reloadOperatingSystem', + args=('FORCE', {'lvmFlag': True}), identifier=100) + def test_get_create_options(self): options = self.hardware.get_create_options() From 3863aaf5270ff8d7848371c7a00a3ddd9a620b8d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 23 Jul 2020 10:51:07 -0400 Subject: [PATCH 0405/1582] Refactored Code. --- SoftLayer/CLI/hardware/guests.py | 6 +++--- SoftLayer/CLI/virt/list.py | 7 +++---- SoftLayer/managers/vs.py | 6 +++--- tests/managers/hardware_tests.py | 18 +----------------- tests/managers/vs/vs_tests.py | 27 +-------------------------- 5 files changed, 11 insertions(+), 53 deletions(-) diff --git a/SoftLayer/CLI/hardware/guests.py b/SoftLayer/CLI/hardware/guests.py index 418cf9d6e..238897e84 100644 --- a/SoftLayer/CLI/hardware/guests.py +++ b/SoftLayer/CLI/hardware/guests.py @@ -1,4 +1,4 @@ -"""List the Hardware server associated virtual guests.""" +"""Lists the Virtual Guests running on this server.""" # :license: MIT, see LICENSE for more details. import click @@ -15,14 +15,14 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """List the Hardware server associated virtual guests.""" + """Lists the Virtual Guests running on this server.""" mgr = SoftLayer.HardwareManager(env.client) hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'hardware') hw_guests = mgr.get_hardware_guests(hw_id) if not hw_guests: - raise exceptions.CLIAbort("The hardware server does not has associated virtual guests.") + raise exceptions.CLIAbort("No Virtual Guests found.") table = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState']) table.sortby = 'hostname' diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 925f33d2a..1a3c4d545 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -96,11 +96,10 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, hardware_guests = vsi.get_hardware_guests() for hardware in hardware_guests: - if 'virtualHost' in hardware and hardware['virtualHost']['guests']: + if hardware['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', - 'powerState'], title="Hardware(id = {hardwareId}) guests " - "associated".format(hardwareId=hardware['id']) - ) + 'powerState'], title=title) table_hardware_guest.sortby = 'hostname' for guest in hardware['virtualHost']['guests']: table_hardware_guest.add_row([ diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6c4d67786..a322c78a1 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1160,10 +1160,10 @@ def get_local_disks(self, instance_id): return self.guest.getBlockDevices(mask=mask, id=instance_id) def get_hardware_guests(self): - """Returns the hardware server vs associated. + """Returns all virtualHost capable hardware objects and their guests. :return SoftLayer_Hardware[]. """ - object_filter = {"hardware": {"networkGatewayMemberFlag": {"operation": 0}}} - mask = "mask[networkGatewayMemberFlag,virtualHost[guests[powerState]]]" + object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}} + mask = "mask[virtualHost[guests[powerState]]]" return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index a7824dd61..e98c57492 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -656,23 +656,7 @@ def test_get_hardware_guests(self): result = self.hardware.get_hardware_guests(1234) - self.assertEqual([ - { - "accountId": 11111, - "hostname": "NSX-T Manager", - "id": 22222, - "maxCpu": 16, - "maxCpuUnits": "CORE", - "maxMemory": 49152, - "powerState": { - "keyName": "RUNNING", - "name": "Running" - }, - "status": { - "keyName": "ACTIVE", - "name": "Active" - } - }], result) + self.assertEqual("NSX-T Manager", result[0]['hostname']) class HardwareHelperTests(testing.TestCase): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index fcee57be5..34e07dd06 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1164,29 +1164,4 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() - self.assertEqual([{ - "accountId": 11111, - "domain": "vmware.chechu.com", - "hostname": "host14", - "id": 22222, - "virtualHost": { - "accountId": 11111, - "id": 33333, - "name": "host14.vmware.chechu.com", - "guests": [ - { - "accountId": 11111, - "hostname": "NSX-T Manager", - "id": 44444, - "maxCpu": 16, - "maxCpuUnits": "CORE", - "maxMemory": 49152, - "powerState": { - "keyName": "RUNNING", - "name": "Running" - }, - "status": { - "keyName": "ACTIVE", - "name": "Active" - } - }]}}], result) + self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) From d1c59f925179f8b3109802eda16103ee41eead7e Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 23 Jul 2020 12:11:16 -0400 Subject: [PATCH 0406/1582] Fixed unit test issues. --- SoftLayer/fixtures/SoftLayer_Account.py | 44 +++++++++++++++++++++++-- tests/CLI/modules/vs/vs_tests.py | 13 -------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 95692ef00..dcb6d32f0 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -146,6 +146,28 @@ 'id': 6660 } }, + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "vmware.chechu.com", + "guests": [ + { + "accountId": 11111, + "createDate": "2019-09-05T17:03:42-06:00", + "hostname": "NSX-T Manager", + "id": 33333, + "maxCpu": 16, + "maxCpuUnits": "CORE", + "maxMemory": 49152, + "powerState": { + "keyName": "RUNNING", + "name": "Running" + }, + "status": { + "keyName": "ACTIVE", + "name": "Active" + } + }]} }, { 'id': 1001, 'metricTrackingObject': {'id': 4}, @@ -190,7 +212,13 @@ 'vlanNumber': 3672, 'id': 19082 }, - ] + ], + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }, { 'id': 1002, 'metricTrackingObject': {'id': 5}, @@ -234,9 +262,21 @@ 'vlanNumber': 3672, 'id': 19082 }, - ] + ], + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }, { 'id': 1003, + "virtualHost": { + "accountId": 11111, + "id": 22222, + "name": "host14.vmware.chechu.com", + "guests": [] + } }] getDomains = [{'name': 'example.com', 'id': 12345, diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..783b52743 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -143,19 +143,6 @@ def test_list_vs(self): result = self.run_command(['vs', 'list', '--tag=tag']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'datacenter': 'TEST00', - 'primary_ip': '172.16.240.2', - 'hostname': 'vs-test1', - 'action': None, - 'id': 100, - 'backend_ip': '10.45.19.37'}, - {'datacenter': 'TEST00', - 'primary_ip': '172.16.240.7', - 'hostname': 'vs-test2', - 'action': None, - 'id': 104, - 'backend_ip': '10.45.19.35'}]) @mock.patch('SoftLayer.utils.lookup') def test_detail_vs_empty_billing(self, mock_lookup): From d0ebf61672f3f32230535a599b407a021e958c99 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 09:20:04 -0400 Subject: [PATCH 0407/1582] vs upgrade disk and add new disk --- SoftLayer/CLI/virt/upgrade.py | 12 +++-- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 46 ++++++++++++++++--- SoftLayer/managers/vs.py | 43 ++++++++++++++--- tests/CLI/modules/vs/vs_tests.py | 24 ++++++++++ 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 463fc077e..23e521863 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import click +import json import SoftLayer from SoftLayer.CLI import environment @@ -20,15 +21,17 @@ help="CPU core will be on a dedicated host server.") @click.option('--memory', type=virt.MEM_TYPE, help="Memory in megabytes") @click.option('--network', type=click.INT, help="Network port speed in Mbps") +@click.option('--add', type=click.INT, required=False, help="add Hard disk in GB") +@click.option('--disk', nargs=1, help="update the number and capacity in GB Hard disk, E.G {'number':2,'capacity':100}") @click.option('--flavor', type=click.STRING, help="Flavor keyName\nDo not use --memory, --cpu or --private, if you are using flavors") @environment.pass_env -def cli(env, identifier, cpu, private, memory, network, flavor): +def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): """Upgrade a virtual server.""" vsi = SoftLayer.VSManager(env.client) - if not any([cpu, memory, network, flavor]): + if not any([cpu, memory, network, flavor, disk, add]): raise exceptions.ArgumentError("Must provide [--cpu], [--memory], [--network], or [--flavor] to upgrade") if private and not cpu: @@ -40,6 +43,9 @@ def cli(env, identifier, cpu, private, memory, network, flavor): if memory: memory = int(memory / 1024) + if disk is not None: + disk = json.loads(disk) - if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor): + if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor, + disk=disk, add=add): raise exceptions.CLIAbort('VS Upgrade Failed') diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index c42963c8e..47eebe424 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -8,11 +8,16 @@ 'id': 6327, 'nextInvoiceTotalRecurringAmount': 1.54, 'children': [ - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, - {'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'port_speed', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_core', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'ram', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_core', + 'nextInvoiceTotalRecurringAmount': 1}, + {'categoryCode': 'guest_disk1', + 'nextInvoiceTotalRecurringAmount': 1}, ], 'package': { "id": 835, @@ -622,7 +627,35 @@ 'capacity': '2', 'description': 'RAM', } - }, + }, { + "id": 2255, + "categories": [ + { + "categoryCode": "guest_disk1", + "id": 82, + "name": "Second Disk" + }, + { + "categoryCode": "guest_disk2", + "id": 92, + "name": "Third Disk" + }, + { + "categoryCode": "guest_disk3", + "id": 93, + "name": "Fourth Disk" + }, + { + "categoryCode": "guest_disk4", + "id": 116, + "name": "Fifth Disk" + } + ], + "item": { + "capacity": "10", + "description": "10 GB (SAN)" + } + } ] DEDICATED_GET_UPGRADE_ITEM_PRICES = [ @@ -641,7 +674,6 @@ getMetricTrackingObjectId = 1000 - getBandwidthAllotmentDetail = { 'allocationId': 25465663, 'bandwidthAllotmentId': 138442, diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index e63d7a80f..23f51b138 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -18,6 +18,7 @@ LOGGER = logging.getLogger(__name__) + # pylint: disable=no-self-use,too-many-lines @@ -818,7 +819,8 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): return self.guest.createArchiveTransaction( name, disks_to_capture, notes, id=instance_id) - def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None): + def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, + disk=None, add=None): """Upgrades a VS instance. Example:: @@ -851,7 +853,10 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr if memory is not None and preset is not None: raise ValueError("Do not use memory, private or cpu if you are using flavors") data['memory'] = memory - + if disk is not None: + data['disk'] = disk.get('capacity') + elif add is not None: + data['disk'] = add maintenance_window = datetime.datetime.now(utils.UTC()) order = { 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade', @@ -874,9 +879,30 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr raise exceptions.SoftLayerError( "Unable to find %s option with value %s" % (option, value)) - prices.append({'id': price_id}) - order['prices'] = prices - + if disk is not None: + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk.get('number')), + 'complexType': "SoftLayer_Product_Item_Category" + }], 'complexType': 'SoftLayer_Product_Item_Price'} + prices.append(category) + prices[0]['id'] = price_id + elif add: + vsi_disk = self.get_instance(instance_id) + disk_number = 0 + for item in vsi_disk.get('billingItem').get('children'): + if item.get('categoryCode').__contains__('guest_disk'): + if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): + disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk_number + 1), + 'complexType': "SoftLayer_Product_Item_Category" + }], 'complexType': 'SoftLayer_Product_Item_Price'} + prices.append(category) + prices[0]['id'] = price_id + else: + prices.append({'id': price_id}) + + order['prices'] = prices if preset is not None: vs_object = self.get_instance(instance_id)['billingItem']['package'] order['presetId'] = self.ordering_manager.get_preset_by_key(vs_object['keyName'], preset)['id'] @@ -994,7 +1020,8 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public option_category = { 'memory': 'ram', 'cpus': 'guest_core', - 'nic_speed': 'port_speed' + 'nic_speed': 'port_speed', + 'disk': 'guest_disk' } category_code = option_category.get(option) for price in upgrade_prices: @@ -1006,7 +1033,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public or product.get('units') == 'DEDICATED_CORE') for category in price.get('categories'): - if not (category.get('categoryCode') == category_code + if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) and str(product.get('capacity')) == str(value)): continue @@ -1020,6 +1047,8 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public elif option == 'nic_speed': if 'Public' in product.get('description'): return price.get('id') + elif option == 'disk': + return price.get('id') else: return price.get('id') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a4bf28509..a90811fe8 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -570,6 +570,19 @@ def test_upgrade(self, confirm_mock): self.assertIn({'id': 1122}, order_container['prices']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', + '--disk={"number":1,"capacity":10}']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(799, order_container['presetId']) + self.assertIn({'id': 100}, order_container['virtualGuests']) + self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_flavor(self, confirm_mock): confirm_mock.return_value = True @@ -582,6 +595,17 @@ def test_upgrade_with_flavor(self, confirm_mock): self.assertIn({'id': 100}, order_container['virtualGuests']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_with_add_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--add=10']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertIn({'id': 100}, order_container['virtualGuests']) + self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_cpu_memory_and_flavor(self, confirm_mock): confirm_mock.return_value = True From bb7b220b6f3fcc6c97f53e3c761166960a43ae51 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 11:14:16 -0400 Subject: [PATCH 0408/1582] fix tox tool --- SoftLayer/managers/vs.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 23f51b138..ffc67db7d 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1033,7 +1033,12 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public or product.get('units') == 'DEDICATED_CORE') for category in price.get('categories'): - if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + if option == 'disk': + if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + and str(product.get('capacity')) == str(value)): + return price.get('id') + + if not (category.get('categoryCode') == category_code and str(product.get('capacity')) == str(value)): continue From f1abcfc226095be4c8949c09b2c9c8897626ed37 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 11:57:41 -0400 Subject: [PATCH 0409/1582] fix tox tool --- SoftLayer/CLI/virt/upgrade.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 23e521863..ef4477d86 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -1,9 +1,10 @@ """Upgrade a virtual server.""" # :license: MIT, see LICENSE for more details. -import click import json +import click + import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions From 81bd07df42060b1d2db3848e091878b87a441305 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 24 Jul 2020 16:57:24 -0400 Subject: [PATCH 0410/1582] fix the empty lines --- SoftLayer/CLI/order/preset_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/preset_list.py b/SoftLayer/CLI/order/preset_list.py index 7397f9428..412d95ee7 100644 --- a/SoftLayer/CLI/order/preset_list.py +++ b/SoftLayer/CLI/order/preset_list.py @@ -43,8 +43,8 @@ def cli(env, package_keyname, keyword): for preset in presets: table.add_row([ - preset['name'], - preset['keyName'], - preset['description'] + str(preset['name']).strip(), + str(preset['keyName']).strip(), + str(preset['description']).strip() ]) env.fout(table) From 730ed0fc48b8c0f2257f3aab0bfc0efe1c58e27b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 16:59:16 -0500 Subject: [PATCH 0411/1582] fixed a typo --- SoftLayer/CLI/virt/migrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 5b97ea46b..55ba251ec 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -20,7 +20,7 @@ def cli(env, guest, migrate_all, host): vsi = SoftLayer.VSManager(env.client) pending_filter = {'virtualGuests': {'pendingMigrationFlag': {'operation': 1}}} - dedicated_filer = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} + dedicated_filter = {'virtualGuests': {'dedicatedHost': {'id': {'operation': 'not null'}}}} mask = """mask[ id, hostname, domain, datacenter, pendingMigrationFlag, powerState, primaryIpAddress,primaryBackendIpAddress, dedicatedHost @@ -44,7 +44,7 @@ def cli(env, guest, migrate_all, host): else: click.secho("No guests require migration at this time", fg='green') - migrateable = vsi.list_instances(filter=dedicated_filer, mask=mask) + 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: From 2e15494c935346bd85258b8b4a36f9ba340d0e8e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 17:58:16 -0500 Subject: [PATCH 0412/1582] tox fix --- tests/managers/vs/vs_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index fa2e7464e..a9f256c80 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1133,7 +1133,6 @@ def test_get_local_disks_swap(self): } ], result) - def test_migrate(self): result = self.vs.migrate(1234) self.assertTrue(result) @@ -1175,4 +1174,3 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) - From 47f236c61000ce43b1581ad8d133136cb9e77e02 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 24 Jul 2020 18:18:34 -0500 Subject: [PATCH 0413/1582] added message for empty migrations --- SoftLayer/CLI/virt/migrate.py | 2 ++ tests/CLI/modules/vs/vs_tests.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/SoftLayer/CLI/virt/migrate.py b/SoftLayer/CLI/virt/migrate.py index 55ba251ec..d06673365 100644 --- a/SoftLayer/CLI/virt/migrate.py +++ b/SoftLayer/CLI/virt/migrate.py @@ -60,6 +60,8 @@ def cli(env, guest, migrate_all, host): # 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 diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 60bc8183c..8278ab23f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -836,6 +836,16 @@ def test_vs_migrate_all(self): self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_all_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + mock.return_value = [] + result = self.run_command(['vs', 'migrate', '-a']) + self.assert_no_fail(result) + self.assertIn('No guests require migration at this time', result.output) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) + self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) + self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') + def test_vs_migrate_dedicated(self): result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) self.assert_no_fail(result) From 60fbbbad396f33dad52fd48d72b9ac98d09179b7 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Sun, 26 Jul 2020 12:30:57 -0500 Subject: [PATCH 0414/1582] fixed vs_tests --- tests/CLI/modules/vs/vs_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 8278ab23f..94b913923 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -842,9 +842,6 @@ def test_vs_migrate_all_empty(self): result = self.run_command(['vs', 'migrate', '-a']) self.assert_no_fail(result) self.assertIn('No guests require migration at this time', result.output) - self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) - self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=104) - self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost') def test_vs_migrate_dedicated(self): result = self.run_command(['vs', 'migrate', '-g', '100', '-h', '999']) From ded403848217ff778006c15be6f92fdc5033a62c Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 16:42:22 -0400 Subject: [PATCH 0415/1582] #1302 fix lots of whitespace slcli vs create-options --- SoftLayer/CLI/virt/create_options.py | 189 ++++++++++++++++----------- tests/CLI/modules/vs/vs_tests.py | 93 +++++++------ 2 files changed, 170 insertions(+), 112 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 601c0f3ac..7e671d028 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -18,56 +18,121 @@ def cli(env): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) - result = vsi.get_create_options() + options = vsi.get_create_options() - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' + tables = [ + _get_datacenter_table(options), + _get_flavors_table(options), + _get_cpu_table(options), + _get_memory_table(options), + _get_os_table(options), + _get_disk_table(options), + _get_network_table(options), + ] - # Datacenters + env.fout(formatting.listing(tables, separator='\n')) + + +def _get_datacenter_table(create_options): datacenters = [dc['template']['datacenter']['name'] - for dc in result['datacenters']] + for dc in create_options['datacenters']] + datacenters = sorted(datacenters) - table.add_row(['datacenter', - formatting.listing(datacenters, separator='\n')]) + dc_table = formatting.Table(['datacenter'], title='Datacenters') + dc_table.sortby = 'datacenter' + dc_table.align = 'l' + for datacenter in datacenters: + dc_table.add_row([datacenter]) + return dc_table - _add_flavors_to_table(result, table) - # CPUs - standard_cpus = [int(x['template']['startCpus']) for x in result['processors'] +def _get_flavors_table(create_options): + flavor_table = formatting.Table(['flavor', 'value'], title='Flavors') + flavor_table.sortby = 'flavor' + flavor_table.align = 'l' + grouping = { + 'balanced': {'key_starts_with': 'B1', 'flavors': []}, + 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, + 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, + 'compute': {'key_starts_with': 'C1', 'flavors': []}, + 'memory': {'key_starts_with': 'M1', 'flavors': []}, + 'GPU': {'key_starts_with': 'AC', 'flavors': []}, + 'transient': {'transient': True, 'flavors': []}, + } + + if create_options.get('flavors', None) is None: + return + + for flavor_option in create_options['flavors']: + flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') + + for name, group in grouping.items(): + if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: + if utils.lookup(group, 'transient') is True: + group['flavors'].append(flavor_key_name) + break + + elif utils.lookup(group, 'key_starts_with') is not None \ + and flavor_key_name.startswith(group['key_starts_with']): + group['flavors'].append(flavor_key_name) + break + + for name, group in grouping.items(): + if len(group['flavors']) > 0: + flavor_table.add_row(['{}'.format(name), + formatting.listing(group['flavors'], + separator='\n')]) + return flavor_table + + +def _get_cpu_table(create_options): + cpu_table = formatting.Table(['cpu', 'value'], title='CPUs') + cpu_table.sortby = 'cpu' + cpu_table.align = 'l' + standard_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if not x['template'].get('dedicatedAccountHostOnlyFlag', False) and not x['template'].get('dedicatedHost', None)] - ded_cpus = [int(x['template']['startCpus']) for x in result['processors'] + ded_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if x['template'].get('dedicatedAccountHostOnlyFlag', False)] - ded_host_cpus = [int(x['template']['startCpus']) for x in result['processors'] + ded_host_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] if x['template'].get('dedicatedHost', None)] standard_cpus = sorted(standard_cpus) - table.add_row(['cpus (standard)', formatting.listing(standard_cpus, separator=',')]) + cpu_table.add_row(['standard', formatting.listing(standard_cpus, separator=',')]) ded_cpus = sorted(ded_cpus) - table.add_row(['cpus (dedicated)', formatting.listing(ded_cpus, separator=',')]) + cpu_table.add_row(['dedicated', formatting.listing(ded_cpus, separator=',')]) ded_host_cpus = sorted(ded_host_cpus) - table.add_row(['cpus (dedicated host)', formatting.listing(ded_host_cpus, separator=',')]) + cpu_table.add_row(['dedicated host', formatting.listing(ded_host_cpus, separator=',')]) + return cpu_table + - # Memory - memory = [int(m['template']['maxMemory']) for m in result['memory'] +def _get_memory_table(create_options): + memory_table = formatting.Table(['memory', 'value'], title='Memories') + memory_table.sortby = 'memory' + memory_table.align = 'l' + memory = [int(m['template']['maxMemory']) for m in create_options['memory'] if not m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_memory = [int(m['template']['maxMemory']) for m in result['memory'] + ded_host_memory = [int(m['template']['maxMemory']) for m in create_options['memory'] if m['itemPrice'].get('dedicatedHostInstanceFlag', False)] memory = sorted(memory) - table.add_row(['memory', - formatting.listing(memory, separator=',')]) + memory_table.add_row(['standard', + formatting.listing(memory, separator=',')]) ded_host_memory = sorted(ded_host_memory) - table.add_row(['memory (dedicated host)', - formatting.listing(ded_host_memory, separator=',')]) + memory_table.add_row(['dedicated host', + formatting.listing(ded_host_memory, separator=',')]) + return memory_table - # Operating Systems + +def _get_os_table(create_options): + os_table = formatting.Table(['os', 'value'], title='Operating Systems') + os_table.sortby = 'os' + os_table.align = 'l' op_sys = [o['template']['operatingSystemReferenceCode'] for o in - result['operatingSystems']] + create_options['operatingSystems']] op_sys = sorted(op_sys) os_summary = set() @@ -76,24 +141,29 @@ def cli(env): os_summary.add(operating_system[0:operating_system.find('_')]) for summary in sorted(os_summary): - table.add_row([ - 'os (%s)' % summary, + os_table.add_row([ + summary, os.linesep.join(sorted([x for x in op_sys if x[0:len(summary)] == summary])) ]) + return os_table + - # Disk - local_disks = [x for x in result['blockDevices'] +def _get_disk_table(create_options): + disk_table = formatting.Table(['disk', 'value'], title='Disks') + disk_table.sortby = 'disk' + disk_table.align = 'l' + local_disks = [x for x in create_options['blockDevices'] if x['template'].get('localDiskFlag', False) and not x['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_local_disks = [x for x in result['blockDevices'] + ded_host_local_disks = [x for x in create_options['blockDevices'] if x['template'].get('localDiskFlag', False) and x['itemPrice'].get('dedicatedHostInstanceFlag', False)] - san_disks = [x for x in result['blockDevices'] + san_disks = [x for x in create_options['blockDevices'] if not x['template'].get('localDiskFlag', False)] def add_block_rows(disks, name): @@ -109,18 +179,23 @@ def add_block_rows(disks, name): simple[bid].append(str(block['diskImage']['capacity'])) for label in sorted(simple): - table.add_row(['%s disk(%s)' % (name, label), - formatting.listing(simple[label], - separator=',')]) + disk_table.add_row(['%s disk(%s)' % (name, label), + formatting.listing(simple[label], + separator=',')]) add_block_rows(san_disks, 'san') add_block_rows(local_disks, 'local') add_block_rows(ded_host_local_disks, 'local (dedicated host)') + return disk_table + - # Network +def _get_network_table(create_options): + network_table = formatting.Table(['network', 'value'], title='Network') + network_table.sortby = 'network' + network_table.align = 'l' speeds = [] ded_host_speeds = [] - for option in result['networkComponents']: + for option in create_options['networkComponents']: template = option.get('template', None) price = option.get('itemPrice', None) @@ -140,43 +215,9 @@ def add_block_rows(disks, name): speeds.append(max_speed) speeds = sorted(speeds) - table.add_row(['nic', formatting.listing(speeds, separator=',')]) + network_table.add_row(['nic', formatting.listing(speeds, separator=',')]) ded_host_speeds = sorted(ded_host_speeds) - table.add_row(['nic (dedicated host)', - formatting.listing(ded_host_speeds, separator=',')]) - - env.fout(table) - - -def _add_flavors_to_table(result, table): - grouping = { - 'balanced': {'key_starts_with': 'B1', 'flavors': []}, - 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, - 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, - 'compute': {'key_starts_with': 'C1', 'flavors': []}, - 'memory': {'key_starts_with': 'M1', 'flavors': []}, - 'GPU': {'key_starts_with': 'AC', 'flavors': []}, - 'transient': {'transient': True, 'flavors': []}, - } - - if result.get('flavors', None) is None: - return - - for flavor_option in result['flavors']: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - - for name, group in grouping.items(): - if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: - if utils.lookup(group, 'transient') is True: - group['flavors'].append(flavor_key_name) - break - - elif utils.lookup(group, 'key_starts_with') is not None \ - and flavor_key_name.startswith(group['key_starts_with']): - group['flavors'].append(flavor_key_name) - break - - for name, group in grouping.items(): - if len(group['flavors']) > 0: - table.add_row(['flavors (%s)' % name, formatting.listing(group['flavors'], separator='\n')]) + network_table.add_row(['nic (dedicated host)', + formatting.listing(ded_host_speeds, separator=',')]) + return network_table diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 94b913923..6494f0c40 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -315,28 +315,45 @@ def test_detail_vs_ptr_error(self): def test_create_options(self): result = self.run_command(['vs', 'create-options']) + expected_json_result = [ + [ + {"Datacenter": "ams01"}, + {"Datacenter": "dal05"} + ], + [ + {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, + {"flavor": "balanced local - hdd", "value": ["BL1_1X2X100"]}, + {"flavor": "balanced local - ssd", "value": ["BL2_1X2X100"]}, + {"flavor": "compute", "value": ["C1_1X2X25"]}, + {"flavor": "memory", "value": ["M1_1X2X100"]}, + {"flavor": "GPU", "value": ["AC1_1X2X100", "ACL1_1X2X100"]}, + {"flavor": "transient", "value": ["B1_1X2X25_TRANSIENT"]} + ], + [ + {"cpu": "standard", "value": [1, 2, 3, 4]}, + {"cpu": "dedicated", "value": [1]}, + {"cpu": "dedicated host", "value": [4, 56]} + ], + [ + {"memory": "standard", "value": [1024, 2048, 3072, 4096]}, + {"memory": "dedicated host", "value": [8192, 65536]} + ], + [ + {"os": "CENTOS", "value": "CENTOS_6_64"}, + {"os": "DEBIAN", "value": "DEBIAN_7_64"}, + {"os": "UBUNTU", "value": "UBUNTU_12_64"} + ], + [ + {"disk": "local disk(0)", "value": ["25", "100"]} + ], + [ + {"network": "nic", "value": ["10", "100", "1000"]}, + {"network": "nic (dedicated host)", "value": ["1000"]} + ] + ] self.assert_no_fail(result) - self.assertEqual({'cpus (dedicated host)': [4, 56], - 'cpus (dedicated)': [1], - 'cpus (standard)': [1, 2, 3, 4], - 'datacenter': ['ams01', 'dal05'], - 'flavors (balanced)': ['B1_1X2X25', 'B1_1X2X100'], - 'flavors (balanced local - hdd)': ['BL1_1X2X100'], - 'flavors (balanced local - ssd)': ['BL2_1X2X100'], - 'flavors (compute)': ['C1_1X2X25'], - 'flavors (memory)': ['M1_1X2X100'], - 'flavors (GPU)': ['AC1_1X2X100', 'ACL1_1X2X100'], - 'flavors (transient)': ['B1_1X2X25_TRANSIENT'], - 'local disk(0)': ['25', '100'], - 'memory': [1024, 2048, 3072, 4096], - 'memory (dedicated host)': [8192, 65536], - 'nic': ['10', '100', '1000'], - 'nic (dedicated host)': ['1000'], - 'os (CENTOS)': 'CENTOS_6_64', - 'os (DEBIAN)': 'DEBIAN_7_64', - 'os (UBUNTU)': 'UBUNTU_12_64'}, - json.loads(result.output)) + self.assertEqual(expected_json_result, json.loads(result.output)) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): @@ -357,19 +374,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']) @@ -412,12 +429,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) From 6f873b04971268effc5c365badac3d098504f868 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 16:56:57 -0400 Subject: [PATCH 0416/1582] fix vs tests create options --- tests/CLI/modules/vs/vs_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 6494f0c40..bf0221170 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -317,8 +317,8 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options']) expected_json_result = [ [ - {"Datacenter": "ams01"}, - {"Datacenter": "dal05"} + {"datacenter": "ams01"}, + {"datacenter": "dal05"} ], [ {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, From b8d56f38161b356c2f2c8b8920f7c42c1708b282 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:04:32 -0400 Subject: [PATCH 0417/1582] fix tox issues --- SoftLayer/CLI/virt/create_options.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 7e671d028..85139b8ba 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -62,7 +62,7 @@ def _get_flavors_table(create_options): } if create_options.get('flavors', None) is None: - return + return flavor_table for flavor_option in create_options['flavors']: flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index bf0221170..629b6e14f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -351,7 +351,7 @@ def test_create_options(self): {"network": "nic (dedicated host)", "value": ["1000"]} ] ] - + self.maxDiff = None self.assert_no_fail(result) self.assertEqual(expected_json_result, json.loads(result.output)) From 452922eb17c906ba0a644d25ed5cd2feada46f1b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:56:29 -0400 Subject: [PATCH 0418/1582] fix the VirtTests.test_create_options test --- tests/CLI/modules/vs/vs_tests.py | 44 +++++--------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 629b6e14f..06d3147ac 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -315,45 +315,13 @@ def test_detail_vs_ptr_error(self): def test_create_options(self): result = self.run_command(['vs', 'create-options']) - expected_json_result = [ - [ - {"datacenter": "ams01"}, - {"datacenter": "dal05"} - ], - [ - {"flavor": "balanced", "value": ["B1_1X2X25", "B1_1X2X100"]}, - {"flavor": "balanced local - hdd", "value": ["BL1_1X2X100"]}, - {"flavor": "balanced local - ssd", "value": ["BL2_1X2X100"]}, - {"flavor": "compute", "value": ["C1_1X2X25"]}, - {"flavor": "memory", "value": ["M1_1X2X100"]}, - {"flavor": "GPU", "value": ["AC1_1X2X100", "ACL1_1X2X100"]}, - {"flavor": "transient", "value": ["B1_1X2X25_TRANSIENT"]} - ], - [ - {"cpu": "standard", "value": [1, 2, 3, 4]}, - {"cpu": "dedicated", "value": [1]}, - {"cpu": "dedicated host", "value": [4, 56]} - ], - [ - {"memory": "standard", "value": [1024, 2048, 3072, 4096]}, - {"memory": "dedicated host", "value": [8192, 65536]} - ], - [ - {"os": "CENTOS", "value": "CENTOS_6_64"}, - {"os": "DEBIAN", "value": "DEBIAN_7_64"}, - {"os": "UBUNTU", "value": "UBUNTU_12_64"} - ], - [ - {"disk": "local disk(0)", "value": ["25", "100"]} - ], - [ - {"network": "nic", "value": ["10", "100", "1000"]}, - {"network": "nic (dedicated host)", "value": ["1000"]} - ] - ] - self.maxDiff = None self.assert_no_fail(result) - self.assertEqual(expected_json_result, json.loads(result.output)) + self.assertIn('datacenter', result.output) + self.assertIn('flavor', result.output) + self.assertIn('memory', result.output) + self.assertIn('cpu', result.output) + self.assertIn('OS', result.output) + self.assertIn('network', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): From 9ff03c0f3f2d8b8a4b1c7bd6a7236c732a97d3a8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 24 Jul 2020 19:57:24 -0400 Subject: [PATCH 0419/1582] fix tox analysis issue --- SoftLayer/CLI/virt/create_options.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 85139b8ba..693de74a2 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -1,9 +1,6 @@ """Virtual server order options.""" # :license: MIT, see LICENSE for more details. # pylint: disable=too-many-statements -import os -import os.path - import click import SoftLayer @@ -128,23 +125,21 @@ def _get_memory_table(create_options): def _get_os_table(create_options): - os_table = formatting.Table(['os', 'value'], title='Operating Systems') - os_table.sortby = 'os' + os_table = formatting.Table(['KeyName', 'Description'], title='Operating Systems') + os_table.sortby = 'KeyName' os_table.align = 'l' - op_sys = [o['template']['operatingSystemReferenceCode'] for o in - create_options['operatingSystems']] - - op_sys = sorted(op_sys) - os_summary = set() + op_sys = [] + for operating_system in create_options['operatingSystems']: + os_option = { + 'referenceCode': operating_system['template']['operatingSystemReferenceCode'], + 'description': operating_system['itemPrice']['item']['description'] + } + op_sys.append(os_option) for operating_system in op_sys: - os_summary.add(operating_system[0:operating_system.find('_')]) - - for summary in sorted(os_summary): os_table.add_row([ - summary, - os.linesep.join(sorted([x for x in op_sys - if x[0:len(summary)] == summary])) + operating_system['referenceCode'], + operating_system['description'] ]) return os_table From 1d25ad81141989c871c70170e5ed2841a31fdb79 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 27 Jul 2020 16:56:44 -0500 Subject: [PATCH 0420/1582] added support for filteredMask --- SoftLayer/transports.py | 3 ++- tests/transport_tests.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 02cd7a214..5722e1867 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -564,7 +564,8 @@ def _format_object_mask(objectmask): objectmask = objectmask.strip() if (not objectmask.startswith('mask') and - not objectmask.startswith('[')): + not objectmask.startswith('[') and + not objectmask.startswith('filteredMask')): objectmask = "mask[%s]" % objectmask return objectmask diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 6e6c793fd..a2e500bd8 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -228,6 +228,22 @@ def test_mask_call_v2(self, request): "mask[something[nested]]", 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://something.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]]", + kwargs['data']) + @mock.patch('SoftLayer.transports.requests.Session.request') def test_mask_call_v2_dot(self, request): request.return_value = self.response From 73ea275c20d196c3f9a31d4af3287d1818b692f2 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 28 Jul 2020 18:37:06 -0400 Subject: [PATCH 0421/1582] #1305 update the old Bluemix URLs to the IBM Cloud Docs URL --- CHANGELOG.md | 2 +- SoftLayer/CLI/block/order.py | 2 +- SoftLayer/CLI/file/order.py | 2 +- SoftLayer/CLI/image/export.py | 6 ++++-- SoftLayer/CLI/image/import.py | 10 +++++----- SoftLayer/managers/image.py | 2 +- SoftLayer/managers/vs_capacity.py | 2 +- docs/cli/vs/reserved_capacity.rst | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6b28c4e..de7ae2c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -228,7 +228,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.2...v5.8.3 ## [5.6.0] - 2018-10-16 - Changes: https://github.com/softlayer/softlayer-python/compare/v5.5.3...v5.6.0 -+ #1026 Support for [Reserved Capacity](https://console.bluemix.net/docs/vsi/vsi_about_reserved.html#about-reserved-virtual-servers) ++ #1026 Support for [Reserved Capacity](https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers) * `slcli vs capacity create` * `slcli vs capacity create-guest` * `slcli vs capacity create-options` diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 302b45cc8..738080fd0 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -63,7 +63,7 @@ def cli(env, storage_type, size, iops, tier, os_type, """Order a block storage volume. Valid size and iops options can be found here: - https://console.bluemix.net/docs/infrastructure/BlockStorage/index.html#provisioning + https://cloud.ibm.com/docs/BlockStorage/index.html#provisioning-considerations """ block_manager = SoftLayer.BlockStorageManager(env.client) storage_type = storage_type.lower() diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index 9e1c9cd29..e665a088b 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -52,7 +52,7 @@ def cli(env, storage_type, size, iops, tier, """Order a file storage volume. Valid size and iops options can be found here: - https://console.bluemix.net/docs/infrastructure/FileStorage/index.html#provisioning + https://cloud.ibm.com/docs/FileStorage/index.html#provisioning-considerations """ file_manager = SoftLayer.FileStorageManager(env.client) storage_type = storage_type.lower() diff --git a/SoftLayer/CLI/image/export.py b/SoftLayer/CLI/image/export.py index eb9081ac7..c8215c28c 100644 --- a/SoftLayer/CLI/image/export.py +++ b/SoftLayer/CLI/image/export.py @@ -16,8 +16,10 @@ default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance. For help creating this key see " - "https://console.bluemix.net/docs/services/cloud-object-" - "storage/iam/users-serviceids.html#serviceidapikeys") + "https://cloud.ibm.com/docs/cloud-object-storage?" + "topic=cloud-object-storage-iam-overview#iam-overview" + "-service-id-api-key " + ) @environment.pass_env def cli(env, identifier, uri, ibm_api_key): """Export an image to object storage. diff --git a/SoftLayer/CLI/image/import.py b/SoftLayer/CLI/image/import.py index 53082c9ac..83d2abcf2 100644 --- a/SoftLayer/CLI/image/import.py +++ b/SoftLayer/CLI/image/import.py @@ -22,17 +22,17 @@ default=None, help="The IBM Cloud API Key with access to IBM Cloud Object " "Storage instance and IBM KeyProtect instance. For help " - "creating this key see https://console.bluemix.net/docs/" - "services/cloud-object-storage/iam/users-serviceids.html" - "#serviceidapikeys") + "creating this key see https://cloud.ibm.com/docs/" + "cloud-object-storage?topic=cloud-object-storage" + "-iam-overview#iam-overview-service-id-api-key") @click.option('--root-key-crn', default=None, help="CRN of the root key in your KMS instance") @click.option('--wrapped-dek', default=None, help="Wrapped Data Encryption Key provided by IBM KeyProtect. " - "For more info see https://console.bluemix.net/docs/" - "services/key-protect/wrap-keys.html#wrap-keys") + "For more info see " + "https://cloud.ibm.com/docs/key-protect?topic=key-protect-wrap-keys") @click.option('--cloud-init', is_flag=True, help="Specifies if image is cloud-init") diff --git a/SoftLayer/managers/image.py b/SoftLayer/managers/image.py index d30b05305..b76a959a6 100644 --- a/SoftLayer/managers/image.py +++ b/SoftLayer/managers/image.py @@ -17,7 +17,7 @@ class ImageManager(utils.IdentifierMixin, object): """Manages SoftLayer server images. See product information here: - https://console.bluemix.net/docs/infrastructure/image-templates/image_index.html + https://cloud.ibm.com/docs/image-templates :param SoftLayer.API.BaseClient client: the client instance """ diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 3f6574f12..813e2d565 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -24,7 +24,7 @@ class CapacityManager(utils.IdentifierMixin, object): Product Information - - https://console.bluemix.net/docs/vsi/vsi_about_reserved.html + - https://cloud.ibm.com/docs/virtual-servers?topic=virtual-servers-about-reserved-virtual-servers - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup/ - https://softlayer.github.io/reference/services/SoftLayer_Virtual_ReservedCapacityGroup_Instance/ diff --git a/docs/cli/vs/reserved_capacity.rst b/docs/cli/vs/reserved_capacity.rst index 3193febff..37e74000c 100644 --- a/docs/cli/vs/reserved_capacity.rst +++ b/docs/cli/vs/reserved_capacity.rst @@ -5,8 +5,8 @@ Working with Reserved Capacity There are two main concepts for Reserved Capacity. The `Reserved Capacity Group `_ and the `Reserved Capacity Instance `_ The Reserved Capacity Group, is a set block of capacity set aside for you at the time of the order. It will contain a set number of Instances which are all the same size. Instances can be ordered like normal VSIs, with the exception that you need to include the reservedCapacityGroupId, and it must be the same size as the group you are ordering the instance in. -- `About Reserved Capacity `_ -- `Reserved Capacity FAQ `_ +- `About Reserved Capacity `_ +- `Reserved Capacity FAQ `_ The SLCLI supports some basic Reserved Capacity Features. From 95ad3be5be757c53ecf1b0732237eda4acaf54f8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 29 Jul 2020 19:13:11 -0400 Subject: [PATCH 0422/1582] 1305 update softlayer.com urls to ibm.com/cloud urls --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/dns.py | 2 +- SoftLayer/managers/firewall.py | 2 +- SoftLayer/managers/hardware.py | 2 +- SoftLayer/managers/network.py | 2 +- SoftLayer/managers/object_storage.py | 2 +- SoftLayer/managers/ssl.py | 2 +- SoftLayer/managers/ticket.py | 2 +- SoftLayer/managers/vs.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 4d129d07c..a16500919 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -15,7 +15,7 @@ class BlockStorageManager(StorageManager): """Manages SoftLayer Block Storage volumes. - See product information here: http://www.softlayer.com/block-storage + See product information here: https://www.ibm.com/cloud/block-storage """ def list_block_volume_limit(self): diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 3a2ea9147..1e89ec9cf 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -14,7 +14,7 @@ class DNSManager(utils.IdentifierMixin, object): """Manage SoftLayer DNS. - See product information here: http://www.softlayer.com/DOMAIN-SERVICES + See product information here: https://www.ibm.com/cloud/dns :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 2b1d8e452..34b197521 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -32,7 +32,7 @@ def has_firewall(vlan): class FirewallManager(utils.IdentifierMixin, object): """Manages SoftLayer firewalls - See product information here: http://www.softlayer.com/firewalls + See product information here: https://www.ibm.com/cloud/network-security :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8ac8d7edd..3a7acbfbd 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -41,7 +41,7 @@ class HardwareManager(utils.IdentifierMixin, object): client = SoftLayer.Client() mgr = SoftLayer.HardwareManager(client) - See product information here: http://www.softlayer.com/bare-metal-servers + See product information here: https://www.ibm.com/cloud/bare-metal-servers :param SoftLayer.API.BaseClient client: the client instance :param SoftLayer.managers.OrderingManager ordering_manager: an optional diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index f9c5f4af0..1e9296cac 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -57,7 +57,7 @@ class NetworkManager(object): """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois - See product information here: http://www.softlayer.com/networking + See product information here: https://www.ibm.com/cloud/network :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 2560d26c8..a3a84414b 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -18,7 +18,7 @@ class ObjectStorageManager(object): """Manager for SoftLayer Object Storage accounts. - See product information here: http://www.softlayer.com/object-storage + See product information here: https://www.ibm.com/cloud/object-storage :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/ssl.py b/SoftLayer/managers/ssl.py index 3fa2ac1dd..5474d9cb3 100644 --- a/SoftLayer/managers/ssl.py +++ b/SoftLayer/managers/ssl.py @@ -10,7 +10,7 @@ class SSLManager(object): """Manages SSL certificates in SoftLayer. - See product information here: http://www.softlayer.com/ssl-certificates + See product information here: https://www.ibm.com/cloud/ssl-certificates Example:: diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py index 9ff361d4b..bbd2eddd2 100644 --- a/SoftLayer/managers/ticket.py +++ b/SoftLayer/managers/ticket.py @@ -11,7 +11,7 @@ class TicketManager(utils.IdentifierMixin, object): """Manages SoftLayer support tickets. - See product information here: http://www.softlayer.com/support + See product information here: https://www.ibm.com/cloud/support :param SoftLayer.API.BaseClient client: the client instance diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0e27c4cd3..9995e7ec2 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -24,7 +24,7 @@ class VSManager(utils.IdentifierMixin, object): """Manages SoftLayer Virtual Servers. - See product information here: http://www.softlayer.com/virtual-servers + See product information here: https://www.ibm.com/cloud/virtual-servers Example:: From 194723993b0db8dc7346f9f32b5ed143fad9a8e6 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 3 Aug 2020 19:11:38 -0400 Subject: [PATCH 0423/1582] fix and improve the new code --- SoftLayer/CLI/virt/upgrade.py | 29 +++++++++----- SoftLayer/managers/vs.py | 65 +++++++++++++++++--------------- tests/CLI/modules/vs/vs_tests.py | 40 ++++++++++---------- 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index ef4477d86..4afeb8be6 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -1,8 +1,6 @@ """Upgrade a virtual server.""" # :license: MIT, see LICENSE for more details. -import json - import click import SoftLayer @@ -22,18 +20,20 @@ help="CPU core will be on a dedicated host server.") @click.option('--memory', type=virt.MEM_TYPE, help="Memory in megabytes") @click.option('--network', type=click.INT, help="Network port speed in Mbps") -@click.option('--add', type=click.INT, required=False, help="add Hard disk in GB") -@click.option('--disk', nargs=1, help="update the number and capacity in GB Hard disk, E.G {'number':2,'capacity':100}") +@click.option('--add-disk', type=click.INT, multiple=True, required=False, help="add Hard disk in GB") +@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int), + help="Update disk number to size in GB. --resize-disk 250 2 ") @click.option('--flavor', type=click.STRING, help="Flavor keyName\nDo not use --memory, --cpu or --private, if you are using flavors") @environment.pass_env -def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): +def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize_disk): """Upgrade a virtual server.""" vsi = SoftLayer.VSManager(env.client) - if not any([cpu, memory, network, flavor, disk, add]): - raise exceptions.ArgumentError("Must provide [--cpu], [--memory], [--network], or [--flavor] to upgrade") + if not any([cpu, memory, network, flavor, resize_disk, add_disk]): + raise exceptions.ArgumentError("Must provide [--cpu]," + " [--memory], [--network], [--flavor], [--resize-disk], or [--add] to upgrade") if private and not cpu: raise exceptions.ArgumentError("Must specify [--cpu] when using [--private]") @@ -44,9 +44,18 @@ def cli(env, identifier, cpu, private, memory, network, flavor, disk, add): if memory: memory = int(memory / 1024) - if disk is not None: - disk = json.loads(disk) + if resize_disk: + disk_json = list() + for guest_disk in resize_disk: + disks = {'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_json.append(disks) + + elif add_disk: + disk_json = list() + for guest_disk in add_disk: + disks = {'capacity': guest_disk, 'number': -1} + disk_json.append(disks) if not vsi.upgrade(vs_id, cpus=cpu, memory=memory, nic_speed=network, public=not private, preset=flavor, - disk=disk, add=add): + disk=disk_json): raise exceptions.CLIAbort('VS Upgrade Failed') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index ffc67db7d..672a74701 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -819,8 +819,7 @@ def capture(self, instance_id, name, additional_disks=False, notes=None): return self.guest.createArchiveTransaction( name, disks_to_capture, notes, id=instance_id) - def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, - disk=None, add=None): + def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True, preset=None, disk=None): """Upgrades a VS instance. Example:: @@ -853,10 +852,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr if memory is not None and preset is not None: raise ValueError("Do not use memory, private or cpu if you are using flavors") data['memory'] = memory - if disk is not None: - data['disk'] = disk.get('capacity') - elif add is not None: - data['disk'] = add + maintenance_window = datetime.datetime.now(utils.UTC()) order = { 'complexType': 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade', @@ -867,6 +863,35 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr 'virtualGuests': [{'id': int(instance_id)}], } + if disk: + disk_number = 0 + vsi_disk = self.get_instance(instance_id) + for item in vsi_disk.get('billingItem').get('children'): + if item.get('categoryCode').__contains__('guest_disk'): + if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): + disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) + for disk_guest in disk: + if disk_guest.get('number') > 0: + price_id = self._get_price_id_for_upgrade_option(upgrade_prices, 'disk', + disk_guest.get('capacity'), + public) + disk_number = disk_guest.get('number') + + else: + price_id = self._get_price_id_for_upgrade_option(upgrade_prices, 'disk', + disk_guest.get('capacity'), + public) + disk_number = disk_number + 1 + + category = {'categories': [{ + 'categoryCode': 'guest_disk' + str(disk_number), + 'complexType': "SoftLayer_Product_Item_Category"}], + 'complexType': 'SoftLayer_Product_Item_Price', + 'id': price_id} + + prices.append(category) + order['prices'] = prices + for option, value in data.items(): if not value: continue @@ -879,28 +904,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr raise exceptions.SoftLayerError( "Unable to find %s option with value %s" % (option, value)) - if disk is not None: - category = {'categories': [{ - 'categoryCode': 'guest_disk' + str(disk.get('number')), - 'complexType': "SoftLayer_Product_Item_Category" - }], 'complexType': 'SoftLayer_Product_Item_Price'} - prices.append(category) - prices[0]['id'] = price_id - elif add: - vsi_disk = self.get_instance(instance_id) - disk_number = 0 - for item in vsi_disk.get('billingItem').get('children'): - if item.get('categoryCode').__contains__('guest_disk'): - if disk_number < int("".join(filter(str.isdigit, item.get('categoryCode')))): - disk_number = int("".join(filter(str.isdigit, item.get('categoryCode')))) - category = {'categories': [{ - 'categoryCode': 'guest_disk' + str(disk_number + 1), - 'complexType': "SoftLayer_Product_Item_Category" - }], 'complexType': 'SoftLayer_Product_Item_Price'} - prices.append(category) - prices[0]['id'] = price_id - else: - prices.append({'id': price_id}) + prices.append({'id': price_id}) order['prices'] = prices if preset is not None: @@ -1024,6 +1028,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public 'disk': 'guest_disk' } category_code = option_category.get(option) + for price in upgrade_prices: if price.get('categories') is None or price.get('item') is None: continue @@ -1034,7 +1039,7 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public for category in price.get('categories'): if option == 'disk': - if not (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) + if (category_code == (''.join([i for i in category.get('categoryCode') if not i.isdigit()])) and str(product.get('capacity')) == str(value)): return price.get('id') @@ -1052,8 +1057,6 @@ def _get_price_id_for_upgrade_option(self, upgrade_prices, option, value, public elif option == 'nic_speed': if 'Public' in product.get('description'): return price.get('id') - elif option == 'disk': - return price.get('id') else: return price.get('id') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a90811fe8..138a9f832 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -370,19 +370,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']) @@ -425,12 +425,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) @@ -574,7 +574,7 @@ def test_upgrade(self, confirm_mock): def test_upgrade_disk(self, confirm_mock): confirm_mock.return_value = True result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', - '--disk={"number":1,"capacity":10}']) + '--resize-disk=10', '1', '--resize-disk=10', '2']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] @@ -598,7 +598,7 @@ def test_upgrade_with_flavor(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_add_disk(self, confirm_mock): confirm_mock.return_value = True - result = self.run_command(['vs', 'upgrade', '100', '--add=10']) + result = self.run_command(['vs', 'upgrade', '100', '--add-disk=10', '--add-disk=10']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] From d149472298a5f0c8195ffd598ecb6e25ef3684c8 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 4 Aug 2020 09:29:58 -0400 Subject: [PATCH 0424/1582] fix tox tool --- SoftLayer/CLI/virt/upgrade.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index 4afeb8be6..fdfa37822 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -42,16 +42,15 @@ def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize if not (env.skip_confirmations or formatting.confirm("This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') + disk_json = list() if memory: memory = int(memory / 1024) if resize_disk: - disk_json = list() for guest_disk in resize_disk: disks = {'capacity': guest_disk[0], 'number': guest_disk[1]} disk_json.append(disks) elif add_disk: - disk_json = list() for guest_disk in add_disk: disks = {'capacity': guest_disk, 'number': -1} disk_json.append(disks) From d6583f3c5f346a527a087c4e2c9fdb38ae6cc747 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 4 Aug 2020 15:04:50 -0500 Subject: [PATCH 0425/1582] 5.9.0 changelog entry --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de7ae2c03..92939ef75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [5.9.0] - 2020-08-03 +https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 + +- #1280 Notification Management + + slcli user notifications + + slcli user edit-notifications +- #828 Added networking options to slcli hw create-options + + Refactored slcli hw create to use the ordering manager + + Added --network option to slcli hw create for more granular network choices. + + Deprecated --port-speed and --no-public . They still work for now, but will be removed in a future release. +- #1298 Fix Unhandled exception in CLI - vs detail +- #1309 Fix the empty lines in slcli vs create-options +- #1301 Ability to list VirtualHost capable guests + + slcli hardware guests + + slcli vs list will show guests on VirtualHost servers +- #875 added option to reload bare metal servers with LVM enabled +- #874 Added Migrate command +- #1313 Added support for filteredMask +- #1305 Update docs links +- #1302 Fix lots of whitespace slcli vs create-options ## [5.8.9] - 2020-07-06 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 From 2193048843cac26950bb20efb7286b33b7cbda18 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 4 Aug 2020 17:36:16 -0500 Subject: [PATCH 0426/1582] Support for STDIN on creating and updating tickets. #900 --- SoftLayer/CLI/ticket/create.py | 29 +++++++++++++--- SoftLayer/CLI/ticket/update.py | 29 +++++++++++++--- docs/cli/tickets.rst | 6 ++++ tests/CLI/modules/ticket_tests.py | 55 +++++++++++++++++++++++++++---- 4 files changed, 103 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/ticket/create.py b/SoftLayer/CLI/ticket/create.py index 5ebad8cf4..17667d74f 100644 --- a/SoftLayer/CLI/ticket/create.py +++ b/SoftLayer/CLI/ticket/create.py @@ -12,8 +12,7 @@ @click.command() @click.option('--title', required=True, help="The title of the ticket") @click.option('--subject-id', type=int, required=True, - help="""The subject id to use for the ticket, - issue 'slcli ticket subjects' to get the list""") + help="""The subject id to use for the ticket, run 'slcli ticket subjects' to get the list""") @click.option('--body', help="The ticket body") @click.option('--hardware', 'hardware_identifier', help="The identifier for hardware to attach") @@ -24,11 +23,31 @@ Only settable with Advanced and Premium support. See https://www.ibm.com/cloud/support""") @environment.pass_env def cli(env, title, subject_id, body, hardware_identifier, virtual_identifier, priority): - """Create a support ticket.""" - ticket_mgr = SoftLayer.TicketManager(env.client) + """Create a Infrastructure support ticket. + + Example:: + + Will create the ticket with `Some text`. + + slcli ticket create --body="Some text" --subject-id 1522 --hardware 12345 --title "My New Ticket" + Will create the ticket with text from STDIN + + cat sometfile.txt | slcli ticket create --subject-id 1003 --virtual 111111 --title "Reboot Me" + + Will open the default text editor, and once closed, use that text to create the ticket + + slcli ticket create --subject-id 1482 --title "Vyatta Questions..." + """ + ticket_mgr = SoftLayer.TicketManager(env.client) if body is None: - body = click.edit('\n\n' + ticket.TEMPLATE_MSG) + stdin = click.get_text_stream('stdin') + # Means there is text on the STDIN buffer, read it and add to the ticket + if not stdin.isatty(): + body = stdin.read() + # This is an interactive terminal, open a text editor + else: + body = click.edit('\n\n' + ticket.TEMPLATE_MSG) created_ticket = ticket_mgr.create_ticket( title=title, body=body, diff --git a/SoftLayer/CLI/ticket/update.py b/SoftLayer/CLI/ticket/update.py index 9c10971ad..f04d36f94 100644 --- a/SoftLayer/CLI/ticket/update.py +++ b/SoftLayer/CLI/ticket/update.py @@ -11,16 +11,35 @@ @click.command() @click.argument('identifier') -@click.option('--body', help="The entry that will be appended to the ticket") +@click.option('--body', help="Text to add to the ticket. STDIN or the default text editor will be used otherwise.") @environment.pass_env def cli(env, identifier, body): - """Adds an update to an existing ticket.""" + """Adds an update to an existing ticket. + + Example:: + + Will update the ticket with `Some text`. + + slcli ticket update 123456 --body="Some text" + + Will update the ticket with text from STDIN + + cat sometfile.txt | slcli ticket update 123456 + + Will open the default text editor, and once closed, use that text to update the ticket + + slcli ticket update 123456 + """ mgr = SoftLayer.TicketManager(env.client) ticket_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'ticket') - if body is None: - body = click.edit('\n\n' + ticket.TEMPLATE_MSG) - + stdin = click.get_text_stream('stdin') + # Means there is text on the STDIN buffer, read it and add to the ticket + if not stdin.isatty(): + body = stdin.read() + # This is an interactive terminal, open a text editor + else: + body = click.edit('\n\n' + ticket.TEMPLATE_MSG) mgr.update_ticket(ticket_id=ticket_id, body=body) env.fout("Ticket Updated!") diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst index ad5f23428..71ef3192c 100644 --- a/docs/cli/tickets.rst +++ b/docs/cli/tickets.rst @@ -3,6 +3,12 @@ Support Tickets =============== +The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. +These tickets will still show up in your web portal, but for the more unified case management API, +see the `Case Management API `_ + +.. note:: Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. + .. click:: SoftLayer.CLI.ticket.create:cli :prog: ticket create :show-nested: diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 4f8db62a8..7b2363eab 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -13,6 +13,22 @@ from SoftLayer import testing +class FakeTTY(): + """A fake object to fake STD input""" + def __init__(self, isatty=False, read="Default Output"): + """Sets isatty and read""" + self._isatty = isatty + self._read = read + + def isatty(self): + """returns self.isatty""" + return self._isatty + + def read(self): + """returns self.read""" + return self._read + + class TicketTests(testing.TestCase): def test_list(self): @@ -99,18 +115,33 @@ def test_create_and_attach(self): identifier=100) @mock.patch('click.edit') - def test_create_no_body(self, edit_mock): + @mock.patch('click.get_text_stream') + def test_create_no_body(self, isatty_mock, edit_mock): + fake_tty = FakeTTY(True, "TEST") + isatty_mock.return_value = fake_tty edit_mock.return_value = 'ticket body' - result = self.run_command(['ticket', 'create', '--title=Test', - '--subject-id=1000']) + result = self.run_command(['ticket', 'create', '--title=Test', '--subject-id=1000']) self.assert_no_fail(result) args = ({'subjectId': 1000, 'assignedUserId': 12345, 'title': 'Test'}, 'ticket body') - self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', - args=args) + self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', args=args) + + @mock.patch('click.get_text_stream') + def test_create_no_body_stdin(self, isatty_mock): + fake_tty = FakeTTY(False, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty + result = self.run_command(['ticket', 'create', '--title=Test', '--subject-id=1000']) + print(result.output) + self.assert_no_fail(result) + + args = ({'subjectId': 1000, + 'assignedUserId': 12345, + 'title': 'Test'}, 'TEST TICKET BODY') + + self.assert_called_with('SoftLayer_Ticket', 'createStandardTicket', args=args) def test_subjects(self): list_expected_ids = [1001, 1002, 1003, 1004, 1005] @@ -294,12 +325,24 @@ def test_ticket_update(self): self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing'},), identifier=100) @mock.patch('click.edit') - def test_ticket_update_no_body(self, edit_mock): + @mock.patch('click.get_text_stream') + def test_ticket_update_no_body(self, isatty_mock, edit_mock): + fake_tty = FakeTTY(True, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty edit_mock.return_value = 'Testing1' result = self.run_command(['ticket', 'update', '100']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Ticket', 'addUpdate', args=({'entry': 'Testing1'},), identifier=100) + @mock.patch('click.get_text_stream') + def test_ticket_update_no_body_stdin(self, isatty_mock): + fake_tty = FakeTTY(False, "TEST TICKET BODY") + isatty_mock.return_value = fake_tty + result = self.run_command(['ticket', 'update', '100']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Ticket', 'addUpdate', + args=({'entry': 'TEST TICKET BODY'},), identifier=100) + def test_ticket_json(self): result = self.run_command(['--format=json', 'ticket', 'detail', '1']) expected = {'Case_Number': 'CS123456', From 68f46e294edbaf093bdfa0ed003f8c57ee9bd63a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 11 Aug 2020 09:39:30 -0400 Subject: [PATCH 0427/1582] fix tox Christopher commet code review --- SoftLayer/managers/vs.py | 5 +++++ tests/CLI/modules/vs/vs_tests.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 672a74701..8f799b1ac 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -883,6 +883,11 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=Tr public) disk_number = disk_number + 1 + if price_id is None: + raise exceptions.SoftLayerAPIError(500, + 'Unable to find %s option with value %s' % ( + ('disk', disk_guest.get('capacity')))) + category = {'categories': [{ 'categoryCode': 'guest_disk' + str(disk_number), 'complexType': "SoftLayer_Product_Item_Category"}], diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 138a9f832..0c8092e7f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -583,6 +583,14 @@ def test_upgrade_disk(self, confirm_mock): self.assertIn({'id': 100}, order_container['virtualGuests']) self.assertEqual(order_container['virtualGuests'], [{'id': 100}]) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_error(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vs', 'upgrade', '100', '--flavor=M1_64X512X100', + '--resize-disk=1000', '1', '--resize-disk=10', '2']) + self.assertEqual(result.exit_code, 1) + self.assertIsInstance(result.exception, SoftLayerAPIError) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_with_flavor(self, confirm_mock): confirm_mock.return_value = True From db56ca4f5d4cf3412fb9c7af4e3b104c651770e4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 11 Aug 2020 14:49:41 -0400 Subject: [PATCH 0428/1582] Ordering price information improvements. --- SoftLayer/CLI/hardware/create_options.py | 204 +++++++++++++++--- SoftLayer/CLI/order/item_list.py | 86 +++++++- .../fixtures/SoftLayer_Product_Package.py | 50 ++++- SoftLayer/managers/hardware.py | 51 ++++- SoftLayer/managers/ordering.py | 13 ++ tests/CLI/modules/order_tests.py | 26 +++ tests/CLI/modules/server_tests.py | 26 +++ tests/managers/hardware_tests.py | 98 ++++++++- tests/managers/ordering_tests.py | 34 +++ 9 files changed, 534 insertions(+), 54 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 2e9949d09..f3a3dec14 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -4,13 +4,17 @@ import click from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import hardware @click.command() +@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') +@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' + 'keyName e.g. AMSTERDAM02') @environment.pass_env -def cli(env): +def cli(env, prices, location): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) @@ -22,41 +26,171 @@ def cli(env): dc_table = formatting.Table(['Datacenter', 'Value'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' - for location in options['locations']: - dc_table.add_row([location['name'], location['key']]) + for location_info in options['locations']: + dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - # Presets - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # Operating systems - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Port speed - port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") - port_speed_table.sortby = 'Speed' - port_speed_table.align = 'l' - - for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) - tables.append(port_speed_table) - - # Extras - extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") - extras_table.sortby = 'Value' - extras_table.align = 'l' - for extra in options['extras']: - extras_table.add_row([extra['name'], extra['key']]) - tables.append(extras_table) + if location and prices: + raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + + if prices: + _preset_prices_table(options['sizes'], tables) + _os_prices_table(options['operating_systems'], tables) + _port_speed_prices_table(options['port_speeds'], tables) + _extras_prices_table(options['extras'], tables) + elif location: + _preset_prices_table(options['sizes'], tables) + location_prices = hardware_manager.get_hardware_item_prices(location) + _location_item_prices(location_prices, tables) + else: + # Presets + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) + + # Operating systems + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + # Port speed + port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in options['port_speeds']: + port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) + tables.append(port_speed_table) + + # Extras + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' + extras_table.align = 'l' + for extra in options['extras']: + extras_table.add_row([extra['name'], extra['key']]) + tables.append(extras_table) env.fout(formatting.listing(tables, separator='\n')) + + +def _preset_prices_table(sizes, tables): + """Shows Server Preset options prices. + + :param [] sizes: List of Hardware Server sizes. + :param tables: Table formatting. + """ + preset_prices_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_prices_table.sortby = 'Value' + preset_prices_table.align = 'l' + for size in sizes: + preset_prices_table.add_row([size['name'], size['key'], size['hourlyRecurringFee'], size['recurringFee']]) + tables.append(preset_prices_table) + + +def _os_prices_table(operating_systems, tables): + """Shows Server Operating Systems prices cost and capacity restriction. + + :param [] operating_systems: List of Hardware Server operating systems. + :param tables: Table formatting. + """ + os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'capacityRestrictionMaximum', + 'capacityRestrictionMinimum', 'capacityRestrictionType'], + title="Operating Systems Prices") + os_prices_table.sortby = 'OS Key' + os_prices_table.align = 'l' + for operating_system in operating_systems: + for price in operating_system['prices']: + os_prices_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType')]) + tables.append(os_prices_table) + + +def _port_speed_prices_table(port_speeds, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] port_speeds: List of Hardware Server Port Speeds. + :param tables: Table formatting. + """ + port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType'], title="Network Options Prices") + port_speed_prices_table.sortby = 'Speed' + port_speed_prices_table.align = 'l' + for speed in port_speeds: + for price in speed['prices']: + port_speed_prices_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType')]) + tables.append(port_speed_prices_table) + + +def _extras_prices_table(extras, tables): + """Shows Server extras prices cost and capacity restriction. + + :param [] extras: List of Hardware Server Extras. + :param tables: Table formatting. + """ + extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType'], title="Extras Prices") + extras_prices_table.align = 'l' + for extra in extras: + for price in extra['prices']: + extras_prices_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + tables.append(extras_prices_table) + + +def _get_price_data(price, item): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result + + +def _location_item_prices(location_prices, tables): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', + 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', + 'capacityRestrictionType']) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' + for price in location_prices: + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + tables.append(location_prices_table) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index ad9ae537f..97e9c985e 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -3,19 +3,25 @@ import click from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering from SoftLayer.utils import lookup COLUMNS = ['category', 'keyName', 'description', 'priceId'] +COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] +COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] @click.command() @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter item names.") @click.option('--category', help="Category code to filter items by") +@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') +@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' + 'keyName e.g. AMSTERDAM02') @environment.pass_env -def cli(env, package_keyname, keyword, category): +def cli(env, package_keyname, keyword, category, prices, location): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -34,9 +40,11 @@ def cli(env, package_keyname, keyword, category): slcli order item-list BARE_METAL_SERVER --category os --keyword ubuntu """ - table = formatting.Table(COLUMNS) manager = ordering.OrderingManager(env.client) + if location and prices: + raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + _filter = {'items': {}} if keyword: _filter['items']['description'] = {'operation': '*= %s' % keyword} @@ -47,9 +55,22 @@ def cli(env, package_keyname, keyword, category): sorted_items = sort_items(items) categories = sorted_items.keys() - for catname in sorted(categories): - for item in sorted_items[catname]: - table.add_row([catname, item['keyName'], item['description'], get_price(item)]) + if prices: + table = formatting.Table(COLUMNS_ITEM_PRICES, title="CRMax = CapacityRestrictionMaximum, " + "CRMin = CapacityRestrictionMinimum, " + "CRType = CapacityRestrictionType") + table = _item_list_prices(categories, sorted_items, table) + elif location: + table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="CRMax = CapacityRestrictionMaximum, " + "CRMin = CapacityRestrictionMinimum, " + "CRType = CapacityRestrictionType") + location_prices = manager.get_item_prices_by_location(location, package_keyname) + table = _location_item_prices(location_prices, table) + else: + table = formatting.Table(COLUMNS) + for catname in sorted(categories): + for item in sorted_items[catname]: + table.add_row([catname, item['keyName'], item['description'], get_price(item)]) env.fout(table) @@ -73,3 +94,58 @@ def get_price(item): if not price.get('locationGroupId'): return price.get('id') return 0 + + +def _item_list_prices(categories, sorted_items, table): + """Add the item prices cost and capacity restriction to the table""" + for catname in sorted(categories): + for item in sorted_items[catname]: + for price in item['prices']: + if not price.get('locationGroupId'): + table.add_row([item['keyName'], price['id'], + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), + get_item_price_data(price, 'capacityRestrictionMaximum'), + get_item_price_data(price, 'capacityRestrictionMinimum'), + get_item_price_data(price, 'capacityRestrictionType')]) + return table + + +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 + + +def _location_item_prices(location_prices, table): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + table.sortby = 'keyName' + table.align = 'l' + for price in location_prices: + table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + _get_price_data(price, 'capacityRestrictionMaximum'), + _get_price_data(price, 'capacityRestrictionMinimum'), + _get_price_data(price, 'capacityRestrictionType') + ]) + return table + + +def _get_price_data(price, item): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b131f8916..f38f828e5 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -8,6 +8,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 1245172, + "locationGroupId": '', 'itemId': 935954, 'laborFee': '0', 'onSaleFlag': '', @@ -26,6 +27,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 17129, + "locationGroupId": '', 'itemId': 4097, 'laborFee': '0', 'onSaleFlag': '', @@ -43,6 +45,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 272, + "locationGroupId": '', 'itemId': 186, 'laborFee': '0', 'onSaleFlag': '', @@ -60,6 +63,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 37650, + "locationGroupId": '', 'itemId': 4702, 'laborFee': '0', 'onSaleFlag': '', @@ -80,6 +84,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 21, + "locationGroupId": '', 'itemId': 15, 'laborFee': '0', 'onSaleFlag': '', @@ -97,6 +102,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 420, + "locationGroupId": '', 'itemId': 309, 'laborFee': '0', 'onSaleFlag': '', @@ -114,6 +120,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 906, + "locationGroupId": '', 'itemId': 504, 'laborFee': '0', 'onSaleFlag': '', @@ -130,6 +137,7 @@ 'prices': [{'accountRestrictions': [], 'currentPriceFlag': '', 'id': 22505, + "locationGroupId": '', 'itemId': 4481, 'laborFee': '0', 'onSaleFlag': '', @@ -147,6 +155,7 @@ 'currentPriceFlag': '', 'hourlyRecurringFee': '0', 'id': 1800, + "locationGroupId": '', 'itemId': 439, 'laborFee': '0', 'onSaleFlag': '', @@ -755,7 +764,15 @@ 'isActive': '1', 'keyName': 'S1270_8GB_2X1TBSATA_NORAID', 'name': 'S1270 8GB 2X1TBSATA NORAID', - 'packageId': 200 + 'packageId': 200, + 'prices': [ + { + "hourlyRecurringFee": "1.18", + "id": 165711, + "locationGroupId": '', + "recurringFee": "780", + } + ] } activePreset2 = { @@ -764,7 +781,15 @@ 'isActive': '1', 'keyName': 'DGOLD_6140_384GB_4X960GB_SSD_SED_RAID_10', 'name': 'DGOLD 6140 384GB 4X960GB SSD SED RAID 10', - 'packageId': 200 + 'packageId': 200, + 'prices': [ + { + "hourlyRecurringFee": "1.18", + "id": 165711, + "locationGroupId": '', + "recurringFee": "780", + } + ] } getAllObjects = [{ @@ -790,6 +815,9 @@ "id": 205911, "laborFee": "0", "locationGroupId": 505, + "capacityRestrictionMaximum": "40", + "capacityRestrictionMinimum": "40", + "capacityRestrictionType": "CORE", "item": { "capacity": "0", "description": "Load Balancer Uptime", @@ -1507,7 +1535,14 @@ "id": 449610, "longName": "Montreal 1", "name": "mon01", - "statusId": 2 + "statusId": 2, + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + "sortOrder": 94 + } + ] }, { "id": 449618, @@ -1531,7 +1566,14 @@ "id": 221894, "longName": "Amsterdam 2", "name": "ams02", - "statusId": 2 + "statusId": 2, + "regions": [ + { + "description": "AMS02 POP - Amsterdam", + "keyname": "AMSTERDAM02", + "sortOrder": 12 + } + ] }, { "id": 265592, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8ac8d7edd..d658d6272 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time +from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager -from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -397,7 +397,9 @@ def get_create_options(self): for preset in package['activePresets'] + package['accountRestrictedActivePresets']: sizes.append({ 'name': preset['description'], - 'key': preset['keyName'] + 'key': preset['keyName'], + 'hourlyRecurringFee': _get_preset_cost(preset['prices'], 'hourly'), + 'recurringFee': _get_preset_cost(preset['prices'], 'monthly') }) operating_systems = [] @@ -410,20 +412,23 @@ def get_create_options(self): operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], - 'referenceCode': item['softwareDescription']['referenceCode'] + 'referenceCode': item['softwareDescription']['referenceCode'], + 'prices': get_item_price(item['prices']) }) # Port speeds elif category == 'port_speed': port_speeds.append({ 'name': item['description'], 'speed': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices']) }) # Extras elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices']) }) return { @@ -447,8 +452,8 @@ def _get_package(self): softwareDescription[id,referenceCode,longDescription], prices ], - activePresets, - accountRestrictedActivePresets, + activePresets[prices], + accountRestrictedActivePresets[prices], regions[location[location[priceGroups]]] ''' package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) @@ -742,6 +747,18 @@ def get_hardware_guests(self, instance_id): id=virtual_host['id']) return virtual_host + def get_hardware_item_prices(self, location): + """Returns the hardware server item prices by location. + + :param string location: location to get the item prices. + """ + object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + package = self.ordering_manager.get_package_by_key(self.package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, + id=package['id']) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" @@ -837,3 +854,23 @@ def _get_location(package, location): return region raise SoftLayerError("Could not find valid location for: '%s'" % location) + + +def _get_preset_cost(prices, type_cost): + """Get the preset cost.""" + item_cost = 0.00 + for price in prices: + if type_cost is 'hourly': + item_cost += float(price['hourlyRecurringFee']) + else: + item_cost += float(price['recurringFee']) + return item_cost + + +def get_item_price(prices): + """Get item prices""" + prices_list = [] + for price in prices: + if not price['locationGroupId']: + prices_list.append(price) + return prices_list diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index bf0d09bbf..ed72ee1b6 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -646,3 +646,16 @@ def get_location_id(self, location): if len(datacenter) != 1: raise exceptions.SoftLayerError("Unable to find location: %s" % location) return datacenter[0]['id'] + + def get_item_prices_by_location(self, location, package_keyname): + """Returns the hardware server item prices by location. + + :param string package_keyname: The package for which to get the items. + :param string location: location to get the item prices. + """ + object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + package = self.get_package_by_key(package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, + id=package['id']) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index a82b731fc..7b5a1dbec 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -45,6 +45,32 @@ def test_item_list(self): self.assertIn('testing', result.output) self.assertIn('item2', result.output) + def test_item_list_prices(self): + result = self.run_command(['order', 'item-list', '--prices=true', 'package']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0]['Hourly'], 0.0) + self.assertEqual(output[1]['CRMim'], '-') + self.assertEqual(output[1]['keyName'], 'KeyName015') + self.assert_called_with('SoftLayer_Product_Package', 'getItems') + + def test_item_list_location(self): + result = self.run_command(['order', 'item-list', '--location=AMSTERDAM02', 'package']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0]['Hourly'], '.093') + self.assertEqual(output[1]['keyName'], 'GUEST_DISK_100_GB_LOCAL_3') + self.assertEqual(output[1]['CRMax'], '-') + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_prices_location(self): + result = self.run_command(['order', 'item-list', '--prices=true', '--location=AMSTERDAM02', 'package']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_package_list(self): p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') p_mock.return_value = _get_all_packages() diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3e7bc0ff5..587124a96 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -363,6 +363,32 @@ def test_create_options(self): self.assertEqual(output[0][0]['Value'], 'wdc01') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + def test_create_options_prices(self): + result = self.run_command(['server', 'create-options', '--prices=true']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[3][0]['capacityRestrictionMaximum'], '-') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + + def test_create_options_location(self): + result = self.run_command(['server', 'create-options', '--location=AMSTERDAM02']) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[1][0]['Monthly'], 780.0) + self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_create_options_prices_location(self): + result = self.run_command(['server', 'create-options', '--prices=true', '--location=AMSTERDAM02']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e68989df0..b6cfc7724 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -133,15 +133,107 @@ def test_get_create_options(self): } sizes = { 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID' + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', + 'hourlyRecurringFee': 1.18, + 'recurringFee': 780.0 } - self.assertEqual(options['extras'][0], extras) + self.assertEqual(options['extras'][0]['key'], extras['key']) self.assertEqual(options['locations'][0], locations) - self.assertEqual(options['operating_systems'][0], operating_systems) + self.assertEqual(options['operating_systems'][0]['referenceCode'], + operating_systems['referenceCode']) self.assertEqual(options['port_speeds'][0]['name'], port_speeds['name']) self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_prices(self): + options = self.hardware.get_create_options() + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc01', 'name': 'Washington 1'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'S1270_8GB_2X1TBSATA_NORAID', + 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', + 'hourlyRecurringFee': 1.18, + 'recurringFee': 780.0 + } + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) + + def test_get_hardware_item_prices(self): + options = self.hardware.get_hardware_item_prices("MONTREAL") + item_prices = [ + { + "hourlyRecurringFee": ".093", + "id": 204015, + "recurringFee": "62", + "item": { + "description": "4 x 2.0 GHz or higher Cores", + "id": 859, + "keyName": "GUEST_CORES_4", + }, + "pricingLocationGroup": { + "id": 503, + "locations": [ + { + "id": 449610, + "longName": "Montreal 1", + "name": "mon01", + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + } + ] + } + ] + } + } + ] + + self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) + self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) + def test_get_create_options_package_missing(self): packages = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') packages.return_value = [] diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 0709eea7e..675ac290d 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -771,3 +771,37 @@ def test_get_item_capacity_intel(self): item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) self.assertEqual(24, int(item_capacity)) + + def test_get_item_prices_by_location(self): + options = self.ordering.get_item_prices_by_location("MONTREAL", "MONTREAL") + item_prices = [ + { + "hourlyRecurringFee": ".093", + "id": 204015, + "recurringFee": "62", + "item": { + "description": "4 x 2.0 GHz or higher Cores", + "id": 859, + "keyName": "GUEST_CORES_4", + }, + "pricingLocationGroup": { + "id": 503, + "locations": [ + { + "id": 449610, + "longName": "Montreal 1", + "name": "mon01", + "regions": [ + { + "description": "MON01 - Montreal", + "keyname": "MONTREAL", + } + ] + } + ] + } + } + ] + + self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) + self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) From 33b4f726a009c60ff06dfeec7281e24e2d3008b6 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 11 Aug 2020 15:10:56 -0400 Subject: [PATCH 0429/1582] Fix the tox analysis. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d658d6272..5caaa5005 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,11 +9,11 @@ import socket import time -from SoftLayer import utils from SoftLayer.decoration import retry from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager +from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -860,7 +860,7 @@ def _get_preset_cost(prices, type_cost): """Get the preset cost.""" item_cost = 0.00 for price in prices: - if type_cost is 'hourly': + if type_cost == 'hourly': item_cost += float(price['hourlyRecurringFee']) else: item_cost += float(price['recurringFee']) From f0a64542367d0e689cfc19ccf19c72954334a415 Mon Sep 17 00:00:00 2001 From: try Date: Thu, 13 Aug 2020 16:12:45 +0530 Subject: [PATCH 0430/1582] Review the change --- SoftLayer/CLI/block/refresh.py | 7 ++++--- SoftLayer/fixtures/SoftLayer_Network_Storage.py | 4 ++-- SoftLayer/managers/storage.py | 8 +++++--- tests/CLI/modules/block_tests.py | 2 +- tests/managers/block_tests.py | 8 ++++---- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 66a2f3c22..aebc5e668 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -1,4 +1,4 @@ -"""Refresh a dependent duplicate volume with a snapshot from its parent.""" +"""Refresh a duplicate volume with a snapshot from its parent.""" # :license: MIT, see LICENSE for more details. import click @@ -11,8 +11,9 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + """"Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) - resp = block_manager.refresh_dep_dupe(volume_id, snapshot_id) + resp = block_manager.refresh_dupe(volume_id, snapshot_id) #remove dep_ in refresh_dep_dupe click.echo(resp) + diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 611e88005..6fc9ed3e3 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -234,8 +234,8 @@ 'provisionedCount': 100 } -refreshDependentDuplicate = { - 'dependentDuplicate': 1 +refreshDuplicate = { #remove Dependent from refreshDependentDuplicate + 'DependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index fb5190d85..9f750272c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -415,13 +415,15 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) - def refresh_dep_dupe(self, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent. + def refresh_dupe(self, volume_id, snapshot_id): #remove dep + """"Refresh a duplicate volume with a snapshot from its parent. :param integer volume_id: The id of the volume :param integer snapshot_id: The id of the snapshot """ - return self.client.call('Network_Storage', 'refreshDependentDuplicate', snapshot_id, id=volume_id) + return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) #remove Dependent + + def convert_dep_dupe(self, volume_id): """Convert a dependent duplicate volume to an independent volume. diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e5f6ba8c5..3aeaa0dc6 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -717,7 +717,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) - def test_dep_dupe_refresh(self): + def test_dupe_refresh(self): #remove _dep in test_dep_dupe_refresh result = self.run_command(['block', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 1ba644236..cd99f6221 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1043,13 +1043,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) - def test_refresh_block_depdupe(self): - result = self.block.refresh_dep_dupe(123, snapshot_id=321) - self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + def test_refresh_block_dupe(self): #remove dep in block_depdupe + result = self.block.refresh_dupe(123, snapshot_id=321) #remove dep in refresh_dep_dupe + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) #remove Dependent in refreshDependentDuplicate self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDependentDuplicate', + 'refreshDuplicate', #remove Dependent in refreshDependentDuplicate identifier=123 ) From 2c66f4c368aed25e1bc12e04c9d79abd02da8d5b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 13 Aug 2020 12:45:01 -0400 Subject: [PATCH 0431/1582] #1318 add Drive number in guest drives details using the device number --- SoftLayer/CLI/virt/detail.py | 10 +++------- SoftLayer/CLI/virt/storage.py | 27 +++++++++++++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index c07ef657c..94c0c7994 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -9,7 +9,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers -from SoftLayer.CLI.virt.storage import get_local_type +from SoftLayer.CLI.virt.storage import get_local_storage_table from SoftLayer import utils LOGGER = logging.getLogger(__name__) @@ -35,11 +35,7 @@ def cli(env, identifier, passwords=False, price=False): result = utils.NestedDict(result) local_disks = vsi.get_local_disks(vs_id) - table_local_disks = formatting.Table(['Type', 'Name', 'Capacity']) - for disks in local_disks: - if 'diskImage' in disks: - table_local_disks.add_row([get_local_type(disks), disks['mountType'], - str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + table_local_disks = get_local_storage_table(local_disks) table.add_row(['id', result['id']]) table.add_row(['guid', result['globalIdentifier']]) @@ -173,7 +169,7 @@ def _get_owner_row(result): owner = utils.lookup(result, 'billingItem', 'orderItem', 'order', 'userRecord', 'username') else: owner = formatting.blank() - return(['owner', owner]) + return (['owner', owner]) def _get_vlan_table(result): diff --git a/SoftLayer/CLI/virt/storage.py b/SoftLayer/CLI/virt/storage.py index 802ae32d9..90252207f 100644 --- a/SoftLayer/CLI/virt/storage.py +++ b/SoftLayer/CLI/virt/storage.py @@ -48,11 +48,8 @@ def cli(env, identifier): nas['allowedVirtualGuests'][0]['datacenter']['longName'], nas.get('notes', None)]) - table_local_disks = formatting.Table(['Type', 'Name', 'Capacity'], title="Other storage details") - for disks in local_disks: - if 'diskImage' in disks: - table_local_disks.add_row([get_local_type(disks), disks['mountType'], - str(disks['diskImage']['capacity']) + " " + str(disks['diskImage']['units'])]) + table_local_disks = get_local_storage_table(local_disks) + table_local_disks.title = "Other storage details" env.fout(table_credentials) env.fout(table_iscsi) @@ -64,10 +61,28 @@ def cli(env, identifier): def get_local_type(disks): """Returns the virtual server local disk type. - :param disks: virtual serve local disks. + :param disks: virtual server local disks. """ disk_type = 'System' if 'SWAP' in disks.get('diskImage', {}).get('description', []): disk_type = 'Swap' return disk_type + + +def get_local_storage_table(local_disks): + """Returns a formatting local disk table + + :param local_disks: virtual server local disks. + """ + table_local_disks = formatting.Table(['Type', 'Name', 'Drive', 'Capacity']) + for disk in local_disks: + if 'diskImage' in disk: + table_local_disks.add_row([ + get_local_type(disk), + disk['mountType'], + disk['device'], + "{capacity} {unit}".format(capacity=disk['diskImage']['capacity'], + unit=disk['diskImage']['units']) + ]) + return table_local_disks From abb1da7e5a4840a822b6b1fb1de649d2522fa9ad Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 Aug 2020 14:49:33 -0400 Subject: [PATCH 0432/1582] add vs list hardware and all option --- SoftLayer/CLI/virt/list.py | 50 +++++++++++++++++--------------- tests/CLI/modules/vs/vs_tests.py | 4 +++ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index 1a3c4d545..e80417657 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -52,6 +52,8 @@ @click.option('--hourly', is_flag=True, help='Show only hourly instances') @click.option('--monthly', is_flag=True, help='Show only monthly instances') @click.option('--transient', help='Filter by transient instances', type=click.BOOL) +@click.option('--hardware', is_flag=True, default=False, help='Show the all VSI related to hardware') +@click.option('--all', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -69,7 +71,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit, transient): + hourly, monthly, tag, columns, limit, transient, all, hardware): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -88,27 +90,29 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, table = formatting.Table(columns.columns) table.sortby = sortby - for guest in guests: - table.add_row([value or formatting.blank() - for value in columns.row(guest)]) + if not hardware or all: + for guest in guests: + table.add_row([value or formatting.blank() + for value in columns.row(guest)]) - env.fout(table) + env.fout(table) - hardware_guests = vsi.get_hardware_guests() - for hardware in hardware_guests: - if hardware['virtualHost']['guests']: - title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) - table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', - 'powerState'], title=title) - table_hardware_guest.sortby = 'hostname' - for guest in hardware['virtualHost']['guests']: - table_hardware_guest.add_row([ - guest['id'], - guest['hostname'], - '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), - guest['maxMemory'], - utils.clean_time(guest['createDate']), - guest['status']['keyName'], - guest['powerState']['keyName'] - ]) - env.fout(table_hardware_guest) + if hardware or all: + hardware_guests = vsi.get_hardware_guests() + for hardware in hardware_guests: + if hardware['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) + table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', + 'powerState'], title=title) + table_hardware_guest.sortby = 'hostname' + for guest in hardware['virtualHost']['guests']: + table_hardware_guest.add_row([ + guest['id'], + guest['hostname'], + '%i %s' % (guest['maxCpu'], guest['maxCpuUnits']), + guest['maxMemory'], + utils.clean_time(guest['createDate']), + guest['status']['keyName'], + guest['powerState']['keyName'] + ]) + env.fout(table_hardware_guest) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 06d3147ac..bce887178 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -846,3 +846,7 @@ def test_vs_migrate_exception(self): self.assert_not_called_with('SoftLayer_Account', 'getVirtualGuests') self.assert_called_with('SoftLayer_Virtual_Guest', 'migrate', identifier=100) self.assert_not_called_with('SoftLayer_Virtual_Guest', 'migrateDedicatedHost', args=(999), identifier=100) + + def test_list_vsi(self): + result = self.run_command(['vs', 'list', '--hardware']) + self.assert_no_fail(result) From 487ae59238faf4addb1c031fd28af1ba890af355 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 Aug 2020 17:22:53 -0400 Subject: [PATCH 0433/1582] fix tox tool --- SoftLayer/CLI/virt/list.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/virt/list.py b/SoftLayer/CLI/virt/list.py index e80417657..ffb9fd4a0 100644 --- a/SoftLayer/CLI/virt/list.py +++ b/SoftLayer/CLI/virt/list.py @@ -53,7 +53,7 @@ @click.option('--monthly', is_flag=True, help='Show only monthly instances') @click.option('--transient', help='Filter by transient instances', type=click.BOOL) @click.option('--hardware', is_flag=True, default=False, help='Show the all VSI related to hardware') -@click.option('--all', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') +@click.option('--all-guests', is_flag=True, default=False, help='Show the all VSI and hardware VSIs') @helpers.multi_option('--tag', help='Filter by tags') @click.option('--sortby', help='Column to sort by', @@ -71,7 +71,7 @@ show_default=True) @environment.pass_env def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, - hourly, monthly, tag, columns, limit, transient, all, hardware): + hourly, monthly, tag, columns, limit, transient, hardware, all_guests): """List virtual servers.""" vsi = SoftLayer.VSManager(env.client) @@ -90,22 +90,22 @@ def cli(env, sortby, cpu, domain, datacenter, hostname, memory, network, table = formatting.Table(columns.columns) table.sortby = sortby - if not hardware or all: + if not hardware or all_guests: for guest in guests: table.add_row([value or formatting.blank() for value in columns.row(guest)]) env.fout(table) - if hardware or all: + if hardware or all_guests: hardware_guests = vsi.get_hardware_guests() - for hardware in hardware_guests: - if hardware['virtualHost']['guests']: - title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hardware['id']) + for hd_guest in hardware_guests: + if hd_guest['virtualHost']['guests']: + title = "Hardware(id = {hardwareId}) guests associated".format(hardwareId=hd_guest['id']) table_hardware_guest = formatting.Table(['id', 'hostname', 'CPU', 'Memory', 'Start Date', 'Status', 'powerState'], title=title) table_hardware_guest.sortby = 'hostname' - for guest in hardware['virtualHost']['guests']: + for guest in hd_guest['virtualHost']['guests']: table_hardware_guest.add_row([ guest['id'], guest['hostname'], From 00cc11f4dc2baedd632f039288b501071628e97a Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 09:56:57 -0400 Subject: [PATCH 0434/1582] fix the ha option firewall add and implement unit test --- SoftLayer/CLI/firewall/add.py | 2 +- .../fixtures/SoftLayer_Product_Package.py | 22 +++++++++----- tests/CLI/modules/firewall_tests.py | 29 +++++++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/firewall/add.py b/SoftLayer/CLI/firewall/add.py index 5eba9b778..12c7abef3 100644 --- a/SoftLayer/CLI/firewall/add.py +++ b/SoftLayer/CLI/firewall/add.py @@ -15,7 +15,7 @@ type=click.Choice(['vs', 'vlan', 'server']), help='Firewall type', required=True) -@click.option('--ha', '--high-availability', +@click.option('-ha', '--high-availability', is_flag=True, help='High available firewall option') @environment.pass_env diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index b131f8916..07aefab69 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -833,6 +833,7 @@ 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 1122, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -845,6 +846,7 @@ 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 4477, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -857,6 +859,7 @@ 'itemCategory': {'categoryCode': 'RAM'}, 'prices': [{'id': 1133, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 3, 'name': 'RAM', 'categoryCode': 'ram'}]}], @@ -870,6 +873,7 @@ 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1007, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -884,6 +888,7 @@ 'prices': [{'id': 1144, 'locationGroupId': None, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -898,6 +903,7 @@ 'prices': [{'id': 332211, 'locationGroupId': 1, 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -908,7 +914,7 @@ 'capacity': '1', 'description': '1 GB iSCSI Storage', 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 1121, @@ -924,7 +930,7 @@ 'capacity': '4', 'description': '4 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 8880, @@ -932,7 +938,7 @@ 'capacity': '8', 'description': '8 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 44400, @@ -940,7 +946,7 @@ 'capacity': '4', 'description': '4 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 88800, @@ -948,7 +954,7 @@ 'capacity': '8', 'description': '8 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 88881, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 88881, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 10, @@ -956,7 +962,7 @@ 'capacity': '0', 'description': 'Global IPv4', 'itemCategory': {'categoryCode': 'global_ipv4'}, - 'prices': [{'id': 11, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 11, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 66464, @@ -964,7 +970,7 @@ 'capacity': '64', 'description': '/64 Block Portable Public IPv6 Addresses', 'itemCategory': {'categoryCode': 'static_ipv6_addresses'}, - 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }, { 'id': 610, @@ -972,7 +978,7 @@ 'capacity': '0', 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, - 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], }] getItemPricesISCSI = [ diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index f83022d7e..270619b2f 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -5,6 +5,9 @@ :license: MIT, see LICENSE for more details. """ import json +from unittest import mock + +from SoftLayer.CLI import exceptions from SoftLayer import testing @@ -28,3 +31,29 @@ def test_list_firewalls(self): 'firewall id': 'server:1234', 'server/vlan id': 1, 'type': 'Server - standard'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_vs(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=vlan', '-ha']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_vlan(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=vs']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_add_server(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) + self.assert_no_fail(result) + self.assertIn("Firewall is being created!", result.output) + + def test_add_server_fail(self): + result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) From 8a6cf5e8166e08c769adf962c39a55ac0436c3a4 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 10:19:05 -0400 Subject: [PATCH 0435/1582] fix tox tool --- tests/CLI/modules/firewall_tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 270619b2f..a90038065 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -52,8 +52,3 @@ def test_add_server(self, confirm_mock): result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) self.assert_no_fail(result) self.assertIn("Firewall is being created!", result.output) - - def test_add_server_fail(self): - result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) From fa8c48357225778e4f93a7c1363bb074b03b0d9f Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 Aug 2020 10:26:12 -0400 Subject: [PATCH 0436/1582] fix tox tool --- tests/CLI/modules/firewall_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index a90038065..b3ac8c0b2 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -7,8 +7,6 @@ import json from unittest import mock -from SoftLayer.CLI import exceptions - from SoftLayer import testing From 7a3d4d175e68e30e57be68b6df4046a26fea9565 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 18 Aug 2020 14:44:54 -0500 Subject: [PATCH 0437/1582] v5.9.0 changelog --- CHANGELOG.md | 3 +++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92939ef75..6cb588bfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 - #1313 Added support for filteredMask - #1305 Update docs links - #1302 Fix lots of whitespace slcli vs create-options +- #900 Support for STDIN on creating and updating tickets. +- #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 https://github.com/softlayer/softlayer-python/compare/v5.8.8...v5.8.9 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cdcdf07ed..8dd619aa4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.8.9' +VERSION = 'v5.9.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 8b8308901..33df75d76 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.8.9', + version='5.9.0', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 169837d621c2e5e2b82e6a787c5eb74135065a67 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:35:18 -0500 Subject: [PATCH 0438/1582] #972 added BluePages and IntegratedOfferingTeam_Region to list of prefixes to not add SoftLayer_ to --- SoftLayer/API.py | 9 ++++----- SoftLayer/fixtures/BluePages_Search.py | 1 + tests/api_tests.py | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/fixtures/BluePages_Search.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index b20b13aaa..e353ae1af 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -162,8 +162,7 @@ def authenticate_with_password(self, username, password, :param string username: your SoftLayer username :param string password: your SoftLayer password :param int security_question_id: The security question id to answer - :param string security_question_answer: The answer to the security - question + :param string security_question_answer: The answer to the security question """ self.auth = None @@ -202,8 +201,7 @@ def call(self, service, method, *args, **kwargs): :param dict raw_headers: (optional) HTTP transport headers :param int limit: (optional) return at most this many results :param int offset: (optional) offset results by this many - :param boolean iter: (optional) if True, returns a generator with the - results + :param boolean iter: (optional) if True, returns a generator with the results :param bool verify: verify SSL cert :param cert: client certificate path @@ -224,7 +222,8 @@ def call(self, service, method, *args, **kwargs): raise TypeError( 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs)) - if self._prefix and not service.startswith(self._prefix): + prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') + if self._prefix and not service.startswith(prefixes): service = self._prefix + service http_headers = {'Accept': '*/*'} diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py new file mode 100644 index 000000000..24830c54f --- /dev/null +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -0,0 +1 @@ +findBluePagesProfile = True \ No newline at end of file diff --git a/tests/api_tests.py b/tests/api_tests.py index 4f1a31e66..39f596b3c 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -259,6 +259,11 @@ def test_call_compression_override(self): headers = calls[0].transport_headers self.assertEqual(headers.get('accept-encoding'), 'gzip') + def test_special_services(self): + # Tests for the special classes that don't need to start with SoftLayer_ + self.client.call('BluePages_Search', 'findBluePagesProfile') + self.assert_called_with('BluePages_Search', 'findBluePagesProfile') + class UnauthenticatedAPIClient(testing.TestCase): def set_up(self): From 1f1e00326e559397d630a045a98f3ed6df6b86fa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:47:38 -0500 Subject: [PATCH 0439/1582] fixed whitespace issue --- SoftLayer/API.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index e353ae1af..430ed2d14 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -222,7 +222,7 @@ def call(self, service, method, *args, **kwargs): raise TypeError( 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs)) - prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') + prefixes = (self._prefix, 'BluePages_Search', 'IntegratedOfferingTeam_Region') if self._prefix and not service.startswith(prefixes): service = self._prefix + service From d1358b1e66a49d4c13cbfbb74f652bb025792622 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 21 Aug 2020 15:57:24 -0500 Subject: [PATCH 0440/1582] tox issue --- SoftLayer/fixtures/BluePages_Search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/BluePages_Search.py b/SoftLayer/fixtures/BluePages_Search.py index 24830c54f..9682f63dc 100644 --- a/SoftLayer/fixtures/BluePages_Search.py +++ b/SoftLayer/fixtures/BluePages_Search.py @@ -1 +1 @@ -findBluePagesProfile = True \ No newline at end of file +findBluePagesProfile = True From f0f5a21afbd41a8731b285b4288c6a62561d1223 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 25 Aug 2020 11:45:13 -0400 Subject: [PATCH 0441/1582] implement unit test to classes not pass the coverage --- SoftLayer/fixtures/SoftLayer_Account.py | 31 ++++++++++-- .../fixtures/SoftLayer_Network_Subnet.py | 1 + .../SoftLayer_Network_Subnet_IpAddress.py | 21 ++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 1 + .../SoftLayer_Network_Vlan_Firewall.py | 43 +++++++++++++++- .../SoftLayer_Security_Certificate.py | 20 +++++++- tests/CLI/modules/firewall_tests.py | 49 +++++++++++++++++-- tests/CLI/modules/globalip_tests.py | 19 +++++++ tests/CLI/modules/ssl_tests.py | 36 ++++++++++++++ tests/CLI/modules/subnet_tests.py | 27 ++++++++++ tests/CLI/modules/vlan_tests.py | 5 ++ tests/CLI/modules/vs/vs_tests.py | 8 +++ 12 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 tests/CLI/modules/ssl_tests.py diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index dcb6d32f0..04a29de96 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -397,7 +397,8 @@ getSecurityCertificates = [{'certificate': '1234', 'commonName': 'cert', - 'id': 1234}] + 'id': 1234, + 'validityDays': 0, }] getExpiredSecurityCertificates = getSecurityCertificates getValidSecurityCertificates = getSecurityCertificates @@ -490,6 +491,7 @@ 'networkSpace': 'PRIVATE', 'hardwareCount': 0, 'hardware': [], + 'vlanNumber': 12, 'networkComponents': [], 'primaryRouter': { 'datacenter': {'name': 'dal00'} @@ -502,13 +504,26 @@ 'totalPrimaryIpAddressCount': 1, 'subnetCount': 0, 'subnets': [], + 'firewallInterfaces': [ + { + 'id': 1, + 'name': 'outside' + }, + { + 'id': 12, + 'name': 'inside' + } + ] }, { 'id': 2, 'networkSpace': 'PRIVATE', 'totalPrimaryIpAddressCount': 2, 'dedicatedFirewallFlag': False, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 7896}, 'hardwareCount': 0, 'hardware': [], + 'vlanNumber': 13, 'networkComponents': [], 'primaryRouter': { 'datacenter': {'name': 'dal00'} @@ -523,6 +538,8 @@ 'id': 1234, 'networkComponent': {'downlinkComponent': {'hardwareId': 1}}, 'status': 'ok'}], + 'firewallInterfaces': [], + 'subnetCount': 0, 'subnets': [], }, { @@ -530,11 +547,20 @@ 'networkSpace': 'PRIVATE', 'name': 'dal00', 'hardwareCount': 1, + 'dedicatedFirewallFlag': True, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 23456}, + 'vlanNumber': 14, 'hardware': [{'id': 1}], 'networkComponents': [{'id': 2}], 'primaryRouter': { 'datacenter': {'name': 'dal00'} }, + 'firewallInterfaces': [ + { + 'id': 31, + 'name': 'outside' + }], 'totalPrimaryIpAddressCount': 3, 'subnetCount': 0, 'subnets': [], @@ -662,7 +688,6 @@ 'id': 12345 }] - getUsers = [ {'displayName': 'ChristopherG', 'hardwareCount': 138, @@ -742,7 +767,6 @@ } ] - getPlacementGroups = [{ "createDate": "2019-01-18T16:08:44-06:00", "id": 12345, @@ -893,7 +917,6 @@ } ] - getPortableStorageVolumes = [ { "capacity": 200, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py index 24683da15..ac3b9d74a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet.py @@ -43,3 +43,4 @@ editNote = True setTags = True +cancel = True diff --git a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py index 15778d238..46ae38cf0 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Subnet_IpAddress.py @@ -6,6 +6,27 @@ 'isNetwork': False, 'isReserved': False, 'subnetId': 5678, + "hardware": { + "id": 12856, + "fullyQualifiedDomainName": "unit.test.com" + }, + "subnet": { + "broadcastAddress": "10.0.1.91", + "cidr": 26, + "gateway": "10.47.16.129", + "id": 258369, + "isCustomerOwned": False, + "isCustomerRoutable": False, + "modifyDate": "2019-04-02T13:45:52-06:00", + "netmask": "255.255.255.192", + "networkIdentifier": "10.0.1.38", + "networkVlanId": 1236987, + "sortOrder": "0", + "subnetType": "PRIMARY", + "totalIpAddresses": "64", + "usableIpAddressCount": "61", + "version": 4 + } } editObject = True diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index b18632534..758fe3b39 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -10,3 +10,4 @@ editObject = True setTags = True +getList = [getObject] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py index c956bf5c0..5d78cf53b 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py @@ -41,7 +41,45 @@ ] } ] - } + }, + "rules": [ + {'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 1, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 80, + 'sourceIpSubnetMask': '0.0.0.0', + 'destinationPortRangeEnd': 80, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '0.0.0.0' + }, + { + 'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 2, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 1, + 'sourceIpSubnetMask': '255.255.255.255', + 'destinationPortRangeEnd': 65535, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '193.212.1.10' + }, + { + 'destinationIpAddress': 'any on server', + 'protocol': 'tcp', + 'orderValue': 3, + 'destinationIpSubnetMask': '255.255.255.255', + 'destinationPortRangeStart': 80, + 'sourceIpSubnetMask': '0.0.0.0', + 'destinationPortRangeEnd': 800, + 'version': 4, + 'action': 'permit', + 'sourceIpAddress': '0.0.0.0' + } + ] + } getRules = [ @@ -59,7 +97,7 @@ }, { 'destinationIpAddress': 'any on server', - 'protocol': 'tcp', + 'protocol': 'tmp', 'orderValue': 2, 'destinationIpSubnetMask': '255.255.255.255', 'destinationPortRangeStart': 1, @@ -82,3 +120,4 @@ 'sourceIpAddress': '0.0.0.0' } ] +edit = True diff --git a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py index fba859384..9f79ca2cc 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py @@ -1,4 +1,22 @@ -getObject = {} +getObject = { + "certificate": "-----BEGIN CERTIFICATE----- \nMIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT" + " -----END CERTIFICATE-----", + "certificateSigningRequest": "-----BEGIN CERTIFICATE REQUEST-----\n" + "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\n" + "-----END CERTIFICATE REQUEST-----", + "commonName": "techbabble.xyz", + "createDate": "2016-01-20T10:56:44-06:00", + "id": 123456, + "intermediateCertificate": "", + "keySize": 258369, + "modifyDate": "2019-06-05T14:10:40-06:00", + "organizationName": "Unspecified", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxf67Ayf07eutrK8OAjWXtwQSdE\n" + "-----END RSA PRIVATE KEY-----", + "validityBegin": "2016-01-19T17:59:37-06:00", + "validityDays": 0, + "validityEnd": "2017-01-20T13:48:41-06:00" +} createObject = {} editObject = True deleteObject = True diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index b3ac8c0b2..7362f1557 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -17,10 +17,14 @@ def test_list_firewalls(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'type': 'VLAN - dedicated', + [{'features': ['HA'], + 'firewall id': 'vlan:1234', 'server/vlan id': 1, - 'features': ['HA'], - 'firewall id': 'vlan:1234'}, + 'type': 'VLAN - dedicated'}, + {'features': ['HA'], + 'firewall id': 'vlan:23456', + 'server/vlan id': 3, + 'type': 'VLAN - dedicated'}, {'features': '-', 'firewall id': 'vs:1234', 'server/vlan id': 1, @@ -50,3 +54,42 @@ def test_add_server(self, confirm_mock): result = self.run_command(['firewall', 'add', '1000', '--firewall-type=server']) self.assert_no_fail(result) self.assertIn("Firewall is being created!", result.output) + + def test_detail(self): + result = self.run_command(['firewall', 'detail', 'vlan:1234']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + [{'#': 1, + 'action': 'permit', + 'dest': 'any on server:80-80', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}, + {'#': 2, + 'action': 'permit', + 'dest': 'any on server:1-65535', + 'dest_mask': '255.255.255.255', + 'protocol': 'tmp', + 'src_ip': '193.212.1.10', + 'src_mask': '255.255.255.255'}, + {'#': 3, + 'action': 'permit', + 'dest': 'any on server:80-800', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_cancel_firewall(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'cancel', 'vlan:1234']) + self.assert_no_fail(result) + self.assertIn("Firewall with id vlan:1234 is being cancelled!", result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_edit(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['firewall', 'edit', 'vlan:1234']) + print(result.output) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 00716c563..6f2ee40d5 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -66,3 +66,22 @@ def test_ip_list(self): 'id': '201', 'ip': '127.0.0.1', 'target': '127.0.0.1 (example.com)'}]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['globalip', 'create', '-v6']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{ + "item": "this is a thing", + "cost": "2.00" + }, + { + "item": "Total monthly cost", + "cost": "2.00" + }]) + + def test_ip_unassign(self): + result = self.run_command(['globalip', 'unassign', '1']) + self.assert_no_fail(result) + self.assertEqual(result.output, "") diff --git a/tests/CLI/modules/ssl_tests.py b/tests/CLI/modules/ssl_tests.py new file mode 100644 index 000000000..79b04df41 --- /dev/null +++ b/tests/CLI/modules/ssl_tests.py @@ -0,0 +1,36 @@ +""" + SoftLayer.tests.CLI.modules.ssl_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import testing + +import json +import mock + + +class SslTests(testing.TestCase): + def test_list(self): + result = self.run_command(['ssl', 'list', '--status', 'all']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [ + { + "id": 1234, + "common_name": "cert", + "days_until_expire": 0, + "notes": None + } + ]) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_remove(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['ssl', 'remove', '123456']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) + + def test_download(self): + result = self.run_command(['ssl', 'download', '123456']) + self.assert_no_fail(result) + self.assertEqual(result.exit_code, 0) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 52de2cd27..57e7dbbb4 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -157,3 +157,30 @@ def test_editrou_Id(self): result = self.run_command(['subnet', 'edit-ip', '123456', '--note=test']) self.assert_no_fail(result) self.assertTrue(result) + + def test_lookup(self): + result = self.run_command(['subnet', 'lookup', '1.2.3.10']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), {'device': { + 'id': 12856, + 'name': 'unit.test.com', + 'type': 'server'}, + "id": 12345, + "ip": "10.0.1.37", + "subnet": { + "id": 258369, + "identifier": "10.0.1.38/26", + "netmask": "255.255.255.192", + "gateway": "10.47.16.129", + "type": "PRIMARY" + }}) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_cancel(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['subnet', 'cancel', '1234']) + self.assert_no_fail(result) + + def test_cancel_fail(self): + result = self.run_command(['subnet', 'cancel', '1234']) + self.assertEqual(result.exit_code, 2) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 1cdd3f3f5..ee606f513 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -94,3 +94,8 @@ def test_vlan_edit_failure(self, click): click.secho.assert_called_with('Failed to edit the vlan', fg='red') self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + + def test_vlan_list(self): + result = self.run_command(['vlan', 'list']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 0a5520121..c9b6c9c0b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -882,3 +882,11 @@ def test_vs_migrate_exception(self): def test_list_vsi(self): result = self.run_command(['vs', 'list', '--hardware']) self.assert_no_fail(result) + + def test_credentail(self): + result = self.run_command(['vs', 'credentials', '100']) + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), [{ + "username": "user", + "password": "pass" + }]) From 689c6ff10954b7c68a30ca038e5a293665b71c99 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 Aug 2020 13:45:54 -0500 Subject: [PATCH 0442/1582] #1330 TOX fixes for the latest version --- SoftLayer/CLI/exceptions.py | 6 +++--- SoftLayer/CLI/formatting.py | 4 ++-- SoftLayer/CLI/hardware/dns.py | 5 +++-- SoftLayer/CLI/metadata.py | 9 ++++----- SoftLayer/CLI/report/bandwidth.py | 6 +++--- SoftLayer/CLI/virt/dns.py | 5 +++-- SoftLayer/fixtures/SoftLayer_Account.py | 3 --- SoftLayer/managers/ordering.py | 13 +++++-------- SoftLayer/managers/storage_utils.py | 14 ++++++-------- SoftLayer/managers/vs_capacity.py | 4 ++-- SoftLayer/testing/__init__.py | 4 ++-- SoftLayer/transports.py | 14 ++++++++------ 12 files changed, 41 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index e3ae7ef45..611d854ea 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -12,7 +12,7 @@ class CLIHalt(SystemExit): """Smoothly halt the execution of the command. No error.""" def __init__(self, code=0, *args): - super(CLIHalt, self).__init__(*args) + super().__init__(*args) self.code = code def __str__(self): @@ -26,7 +26,7 @@ class CLIAbort(CLIHalt): """Halt the execution of the command. Gives an exit code of 2.""" def __init__(self, msg, *args): - super(CLIAbort, self).__init__(code=2, *args) + super().__init__(code=2, *args) self.message = msg @@ -34,5 +34,5 @@ class ArgumentError(CLIAbort): """Halt the execution of the command because of invalid arguments.""" def __init__(self, msg, *args): - super(ArgumentError, self).__init__(msg, *args) + super().__init__(msg, *args) self.message = "Argument Error: %s" % msg diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index d02ceb1a1..0462197c0 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -235,7 +235,7 @@ class SequentialOutput(list): def __init__(self, separator=os.linesep, *args, **kwargs): self.separator = separator - super(SequentialOutput, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_python(self): """returns itself, since it itself is a list.""" @@ -252,7 +252,7 @@ def default(self, obj): """Encode object if it implements to_python().""" if hasattr(obj, 'to_python'): return obj.to_python() - return super(CLIJSONEncoder, self).default(obj) + return super().default(obj) class Table(object): diff --git a/SoftLayer/CLI/hardware/dns.py b/SoftLayer/CLI/hardware/dns.py index 3b7458003..9d8810920 100644 --- a/SoftLayer/CLI/hardware/dns.py +++ b/SoftLayer/CLI/hardware/dns.py @@ -60,5 +60,6 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): # done this way to stay within 80 character lines ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) + except KeyError as ex: + message = "{} does not have an ipv6 address".format(instance['fullyQualifiedDomainName']) + raise exceptions.CLIAbort(message) from ex diff --git a/SoftLayer/CLI/metadata.py b/SoftLayer/CLI/metadata.py index 26d6f2d48..754974885 100644 --- a/SoftLayer/CLI/metadata.py +++ b/SoftLayer/CLI/metadata.py @@ -54,11 +54,10 @@ def cli(env, prop): meta_prop = META_MAPPING.get(prop) or prop env.fout(SoftLayer.MetadataManager().get(meta_prop)) - except SoftLayer.TransportError: - raise exceptions.CLIAbort( - 'Cannot connect to the backend service address. Make sure ' - 'this command is being ran from a device on the backend ' - 'network.') + except SoftLayer.TransportError as ex: + message = 'Cannot connect to the backend service address. Make sure '\ + 'this command is being ran from a device on the backend network.' + raise exceptions.CLIAbort(message) from ex def get_network(): diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index e2b15d981..23d1a157c 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -19,9 +19,9 @@ def _validate_datetime(ctx, param, value): try: return datetime.datetime.strptime(value, "%Y-%m-%d %H:%M:%S") - except (ValueError, TypeError): - raise click.BadParameter( - "not in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") + except (ValueError, TypeError) as ex: + message = "not in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'" + raise click.BadParameter(message) from ex def _get_pooled_bandwidth(env, start, end): diff --git a/SoftLayer/CLI/virt/dns.py b/SoftLayer/CLI/virt/dns.py index 26b4904cd..dfcc3003e 100644 --- a/SoftLayer/CLI/virt/dns.py +++ b/SoftLayer/CLI/virt/dns.py @@ -60,5 +60,6 @@ def cli(env, identifier, a_record, aaaa_record, ptr, ttl): # done this way to stay within 80 character lines ipv6 = instance['primaryNetworkComponent']['primaryVersion6IpAddressRecord']['ipAddress'] dns.sync_host_record(zone_id, instance['hostname'], ipv6, 'aaaa', ttl) - except KeyError: - raise exceptions.CLIAbort("%s does not have an ipv6 address" % instance['fullyQualifiedDomainName']) + except KeyError as ex: + message = "{} does not have an ipv6 address".format(instance['fullyQualifiedDomainName']) + raise exceptions.CLIAbort(message) from ex diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index dcb6d32f0..fa0678cc5 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1,6 +1,3 @@ -# -*- coding: UTF-8 -*- - -# # pylint: disable=bad-continuation getPrivateBlockDeviceTemplateGroups = [{ 'accountId': 1234, 'blockDevices': [], diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index bf0d09bbf..9bedd4005 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -12,10 +12,7 @@ from SoftLayer import exceptions -CATEGORY_MASK = '''id, - isRequired, - itemCategory[id, name, categoryCode] - ''' +CATEGORY_MASK = '''id, isRequired, itemCategory[id, name, categoryCode]''' ITEM_MASK = '''id, keyName, description, itemCategory, categories, prices''' @@ -359,10 +356,10 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): # keyName with the current item we are searching for matching_item = [i for i in items if i['keyName'] == item_keyname][0] - except IndexError: - raise exceptions.SoftLayerError( - "Item {} does not exist for package {}".format(item_keyname, - package_keyname)) + except IndexError as ex: + message = "Item {} does not exist for package {}".format(item_keyname, + package_keyname) + raise exceptions.SoftLayerError(message) from ex # we want to get the price ID that has no location attached to it, # because that is the most generic price. verifyOrder/placeOrder diff --git a/SoftLayer/managers/storage_utils.py b/SoftLayer/managers/storage_utils.py index 0d82d5e25..4419d4d62 100644 --- a/SoftLayer/managers/storage_utils.py +++ b/SoftLayer/managers/storage_utils.py @@ -533,10 +533,9 @@ def prepare_volume_order_object(manager, storage_type, location, size, # Find the ID for the requested location try: location_id = get_location_id(manager, location) - except ValueError: - raise exceptions.SoftLayerError( - "Invalid datacenter name specified. " - "Please provide the lower case short name (e.g.: dal09)") + except ValueError as ex: + message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)" + raise exceptions.SoftLayerError(message) from ex # Determine the category code to use for the order (and product package) order_type_is_saas, order_category_code = _get_order_type_and_category( @@ -676,10 +675,9 @@ def prepare_replicant_order_object(manager, snapshot_schedule, location, # Find the ID for the requested location try: location_id = get_location_id(manager, location) - except ValueError: - raise exceptions.SoftLayerError( - "Invalid datacenter name specified. " - "Please provide the lower case short name (e.g.: dal09)") + except ValueError as ex: + message = "Invalid datacenter name specified. Please provide the lower case short name (e.g.: dal09)" + raise exceptions.SoftLayerError(message) from ex # Get sizes and properties needed for the order volume_size = int(volume['capacityGb']) diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 813e2d565..8ce5cf250 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -143,8 +143,8 @@ def create_guest(self, capacity_id, test, guest_object): try: capacity_flavor = capacity['instances'][0]['billingItem']['item']['keyName'] flavor = _flavor_string(capacity_flavor, guest_object['primary_disk']) - except KeyError: - raise SoftLayerError("Unable to find capacity Flavor.") + except KeyError as ex: + raise SoftLayerError("Unable to find capacity Flavor.") from ex guest_object['flavor'] = flavor guest_object['datacenter'] = capacity['backendRouter']['datacenter']['name'] diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index f1404b423..563b02494 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -113,7 +113,7 @@ def setUp(self): # NOQA self.set_up() def tearDown(self): # NOQA - super(TestCase, self).tearDown() + super().tearDown() self.tear_down() self.mocks.clear() @@ -184,7 +184,7 @@ def assertRaises(self, exception, function_callable, *args, **kwds): # pylint: But switching to just using unittest breaks assertRaises because the format is slightly different. This basically just reformats the call so I don't have to re-write a bunch of tests. """ - with super(TestCase, self).assertRaises(exception) as cm: + with super().assertRaises(exception) as cm: function_callable(*args, **kwds) return cm.exception diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 5722e1867..d0afe3d3b 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -142,7 +142,7 @@ 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(SoftLayerListResult, self).__init__(items) + super().__init__(items) class XmlRpcTransport(object): @@ -245,7 +245,7 @@ def __call__(self, request): '-32300': exceptions.TransportError, } _ex = error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError) - raise _ex(ex.faultCode, ex.faultString) + 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: @@ -533,12 +533,14 @@ def __call__(self, call): try: module_path = 'SoftLayer.fixtures.%s' % call.service module = importlib.import_module(module_path) - except ImportError: - raise NotImplementedError('%s fixture is not implemented' % call.service) + 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: - raise NotImplementedError('%s::%s fixture is not implemented' % (call.service, 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""" From 4450dbcc77b8930fa40c8691bff6eaf9aba463a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 25 Aug 2020 15:19:00 -0400 Subject: [PATCH 0443/1582] fix and update the tox tool --- SoftLayer/fixtures/SoftLayer_Security_Certificate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py index 9f79ca2cc..466cfef14 100644 --- a/SoftLayer/fixtures/SoftLayer_Security_Certificate.py +++ b/SoftLayer/fixtures/SoftLayer_Security_Certificate.py @@ -2,7 +2,7 @@ "certificate": "-----BEGIN CERTIFICATE----- \nMIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT" " -----END CERTIFICATE-----", "certificateSigningRequest": "-----BEGIN CERTIFICATE REQUEST-----\n" - "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G\n" + "MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G\n" "-----END CERTIFICATE REQUEST-----", "commonName": "techbabble.xyz", "createDate": "2016-01-20T10:56:44-06:00", @@ -11,7 +11,7 @@ "keySize": 258369, "modifyDate": "2019-06-05T14:10:40-06:00", "organizationName": "Unspecified", - "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxf67Ayf07eutrK8OAjWXtwQSdE\n" + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE\n" "-----END RSA PRIVATE KEY-----", "validityBegin": "2016-01-19T17:59:37-06:00", "validityDays": 0, From 0608306ed24b15b988278e4d6b6f4c87d2f715b4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 16:42:44 -0400 Subject: [PATCH 0444/1582] #1322 add set notes for storage --- SoftLayer/CLI/block/detail.py | 3 ++ SoftLayer/CLI/block/list.py | 15 ++++++++- SoftLayer/CLI/block/set_note.py | 28 ++++++++++++++++ SoftLayer/CLI/file/detail.py | 5 ++- SoftLayer/CLI/file/list.py | 20 +++++++++--- SoftLayer/CLI/file/set_note.py | 28 ++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/file.py | 1 + SoftLayer/managers/storage.py | 32 +++++++++++++------ 10 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 SoftLayer/CLI/block/set_note.py create mode 100644 SoftLayer/CLI/file/set_note.py diff --git a/SoftLayer/CLI/block/detail.py b/SoftLayer/CLI/block/detail.py index 2e7b115e7..e0cdc8ed1 100644 --- a/SoftLayer/CLI/block/detail.py +++ b/SoftLayer/CLI/block/detail.py @@ -110,4 +110,7 @@ def cli(env, volume_id): original_volume_info.add_row(['Original Snapshot Name', block_volume['originalSnapshotName']]) table.add_row(['Original Volume Properties', original_volume_info]) + notes = '{}'.format(block_volume.get('notes', '')) + table.add_row(['Notes', notes]) + env.fout(table) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 948e6c127..7019166b8 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -34,6 +34,7 @@ column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column('notes', ('notes',), mask="notes"), ] DEFAULT_COLUMNS = [ @@ -47,9 +48,12 @@ 'ip_addr', 'lunId', 'active_transactions', - 'rep_partner_count' + 'rep_partner_count', + 'notes' ] +DEFAULT_NOTES_SIZE = 20 + @click.command() @click.option('--username', '-u', help='Volume username') @@ -75,8 +79,17 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby + reduce_notes(block_volumes) + for block_volume in block_volumes: table.add_row([value or formatting.blank() for value in columns.row(block_volume)]) env.fout(table) + + +def reduce_notes(block_volumes): + for block_volume in block_volumes: + if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] + block_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/block/set_note.py b/SoftLayer/CLI/block/set_note.py new file mode 100644 index 000000000..eeef42e8e --- /dev/null +++ b/SoftLayer/CLI/block/set_note.py @@ -0,0 +1,28 @@ +"""Set note for an existing block storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('volume-id') +@click.option('--note', '-n', + type=str, + required=True, + help='Public notes related to a Storage volume') +@environment.pass_env +def cli(env, volume_id, note): + """Set note for an existing block storage volume.""" + block_manager = SoftLayer.BlockStorageManager(env.client) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') + + result = block_manager.volume_set_note(block_volume_id, note) + + if result: + click.echo("Set note successfully!") + + else: + click.echo("Note could not be set! Please verify your options and try again.") diff --git a/SoftLayer/CLI/file/detail.py b/SoftLayer/CLI/file/detail.py index cea86e351..8ab7b726d 100644 --- a/SoftLayer/CLI/file/detail.py +++ b/SoftLayer/CLI/file/detail.py @@ -29,7 +29,7 @@ def cli(env, volume_id): table.add_row(['Type', storage_type]) table.add_row(['Capacity (GB)', "%iGB" % file_volume['capacityGb']]) - used_space = int(file_volume['bytesUsed'])\ + used_space = int(file_volume['bytesUsed']) \ if file_volume['bytesUsed'] else 0 if used_space < (1 << 10): table.add_row(['Used Space', "%dB" % used_space]) @@ -126,4 +126,7 @@ def cli(env, volume_id): original_volume_info.add_row(['Original Snapshot Name', file_volume['originalSnapshotName']]) table.add_row(['Original Volume Properties', original_volume_info]) + notes = '{}'.format(file_volume.get('notes', '')) + table.add_row(['Notes', notes]) + env.fout(table) diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 86028f4ee..731d0ee1c 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -7,7 +7,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting - COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), column_helper.Column('username', ('username',), mask="username"), @@ -18,7 +17,7 @@ 'storage_type', lambda b: b['storageType']['keyName'].split('_').pop(0) if 'storageType' in b and 'keyName' in b['storageType'] - and isinstance(b['storageType']['keyName'], str) + and isinstance(b['storageType']['keyName'], str) else '-', mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), @@ -28,12 +27,13 @@ column_helper.Column('active_transactions', ('activeTransactionCount',), mask="activeTransactionCount"), column_helper.Column('mount_addr', ('fileNetworkMountAddress',), - mask="fileNetworkMountAddress",), + mask="fileNetworkMountAddress", ), column_helper.Column('rep_partner_count', ('replicationPartnerCount',), mask="replicationPartnerCount"), column_helper.Column( 'created_by', ('billingItem', 'orderItem', 'order', 'userRecord', 'username')), + column_helper.Column('notes', ('notes',), mask="notes"), ] DEFAULT_COLUMNS = [ @@ -46,9 +46,12 @@ 'ip_addr', 'active_transactions', 'mount_addr', - 'rep_partner_count' + 'rep_partner_count', + 'notes', ] +DEFAULT_NOTES_SIZE = 20 + @click.command() @click.option('--username', '-u', help='Volume username') @@ -74,8 +77,17 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby + reduce_notes(file_volumes) + for file_volume in file_volumes: table.add_row([value or formatting.blank() for value in columns.row(file_volume)]) env.fout(table) + + +def reduce_notes(file_volumes): + for file_volume in file_volumes: + if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] + file_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/file/set_note.py b/SoftLayer/CLI/file/set_note.py new file mode 100644 index 000000000..7f5162f0a --- /dev/null +++ b/SoftLayer/CLI/file/set_note.py @@ -0,0 +1,28 @@ +"""Set note for an existing File storage volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('volume-id') +@click.option('--note', '-n', + type=str, + required=True, + help='Public notes related to a Storage volume') +@environment.pass_env +def cli(env, volume_id, note): + """Set note for an existing file storage volume.""" + file_manager = SoftLayer.FileStorageManager(env.client) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') + + result = file_manager.volume_set_note(file_volume_id, note) + + if result: + click.echo("Set note successfully!") + + else: + click.echo("Note could not be set! Please verify your options and try again.") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 8d9c8b1e8..ff3e1c180 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -114,6 +114,7 @@ ('block:volume-limits', 'SoftLayer.CLI.block.limit:cli'), ('block:volume-refresh', 'SoftLayer.CLI.block.refresh:cli'), ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), + ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), @@ -147,6 +148,7 @@ ('file:volume-limits', 'SoftLayer.CLI.file.limit:cli'), ('file:volume-refresh', 'SoftLayer.CLI.file.refresh:cli'), ('file:volume-convert', 'SoftLayer.CLI.file.convert:cli'), + ('file:volume-set-note', 'SoftLayer.CLI.file.set_note:cli'), ('firewall', 'SoftLayer.CLI.firewall'), ('firewall:add', 'SoftLayer.CLI.firewall.add:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 611e88005..fff20fd6a 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -215,6 +215,7 @@ ] deleteObject = True +editObject = True allowAccessFromHostList = True removeAccessFromHostList = True failoverToReplicant = True diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index ce1b951c8..1810a90dc 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -102,6 +102,7 @@ def get_file_volume_details(self, volume_id, **kwargs): 'serviceResourceBackendIpAddress,' 'serviceResource[datacenter[name]],' 'replicationSchedule[type[keyname]]]', + 'notes', ] kwargs['mask'] = ','.join(items) return self.get_volume_details(volume_id, **kwargs) diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index fb5190d85..a54da9d70 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -9,6 +9,7 @@ from SoftLayer.managers import storage_utils from SoftLayer import utils + # pylint: disable=too-many-public-methods @@ -65,6 +66,7 @@ def get_volume_details(self, volume_id, **kwargs): 'serviceResourceBackendIpAddress,' 'serviceResource[datacenter[name]],' 'replicationSchedule[type[keyname]]]', + 'notes', ] kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs) @@ -173,10 +175,10 @@ def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=No :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'billingItem[activeChildren,hourlyFlag],'\ - 'storageTierLevel,osType,staasVersion,'\ - 'hasEncryptionAtRest,snapshotCapacityGb,schedules,'\ - 'intervalSchedule,hourlySchedule,dailySchedule,'\ + block_mask = 'billingItem[activeChildren,hourlyFlag],' \ + 'storageTierLevel,osType,staasVersion,' \ + 'hasEncryptionAtRest,snapshotCapacityGb,schedules,' \ + 'intervalSchedule,hourlySchedule,dailySchedule,' \ 'weeklySchedule,storageType[keyName],provisionedIops' block_volume = self.get_volume_details(volume_id, mask=block_mask) @@ -213,9 +215,9 @@ def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, dupl :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,'\ - 'storageType[keyName],capacityGb,originalVolumeSize,'\ - 'provisionedIops,storageTierLevel,osType[keyName],'\ + block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,' \ + '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']) @@ -270,6 +272,16 @@ def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tie return self.client.call('Product_Order', 'placeOrder', order) + def volume_set_note(self, volume_id, note): + """Set the notes for an existing block volume. + + :param volume_id: The ID of the volume to be modified + :param note: the note + :return: Returns true if success + """ + template = {'notes': note} + return self.client.call('SoftLayer_Network_Storage', 'editObject', template, id=volume_id) + def delete_snapshot(self, snapshot_id): """Deletes the specified snapshot object. @@ -295,9 +307,9 @@ def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs): :param boolean upgrade: Flag to indicate if this order is an upgrade :return: Returns a SoftLayer_Container_Product_Order_Receipt """ - object_mask = 'id,billingItem[location,hourlyFlag],'\ - 'storageType[keyName],storageTierLevel,provisionedIops,'\ - 'staasVersion,hasEncryptionAtRest' + object_mask = 'id,billingItem[location,hourlyFlag],' \ + 'storageType[keyName],storageTierLevel,provisionedIops,' \ + '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) From c75e0a9e452173d0dca945aea31d493b2f93ff27 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 16:44:10 -0400 Subject: [PATCH 0445/1582] #1322 add tests for volumen set notes --- tests/CLI/modules/block_tests.py | 20 ++++++++++++++++++++ tests/CLI/modules/file_tests.py | 20 ++++++++++++++++++++ tests/managers/block_tests.py | 3 ++- tests/managers/file_tests.py | 3 ++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index e5f6ba8c5..08e7f0d74 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -64,6 +64,7 @@ def test_volume_detail(self): self.assertEqual({ 'Username': 'username', 'LUN Id': '2', + 'Notes': "{'status': 'available'}", 'Endurance Tier': 'READHEAVY_TIER', 'IOPs': 1000, 'Snapshot Capacity (GB)': '10', @@ -132,6 +133,7 @@ def test_volume_list(self): 'iops': None, 'ip_addr': '10.1.2.3', 'lunId': None, + 'notes': "{'status': 'availabl", 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', @@ -726,3 +728,21 @@ def test_dep_dupe_convert(self): result = self.run_command(['block', 'volume-convert', '102']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.BlockStorageManager.volume_set_note') + def test_volume_set_note(self, set_note): + set_note.return_value = True + + result = self.run_command(['block', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("successfully!", result.output) + + @mock.patch('SoftLayer.BlockStorageManager.volume_set_note') + def test_volume_not_set_note(self, set_note): + set_note.return_value = False + + result = self.run_command(['block', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("Note could not be set!", result.output) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1d64f54ae..f895b87db 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -50,6 +50,7 @@ def test_volume_list(self): 'username': 'user', 'active_transactions': None, 'mount_addr': '127.0.0.1:/TEST', + 'notes': None, 'rep_partner_count': None }], json.loads(result.output)) @@ -137,6 +138,7 @@ def test_volume_detail(self): 'Data Center': 'dal05', 'Type': 'ENDURANCE', 'ID': 100, + 'Notes': "{'status': 'available'}", '# of Active Transactions': '1', 'Ongoing Transaction': 'This is a buffer time in which the customer may cancel the server', 'Replicant Count': '1', @@ -705,3 +707,21 @@ def test_dep_dupe_convert(self): result = self.run_command(['file', 'volume-convert', '102']) self.assert_no_fail(result) + + @mock.patch('SoftLayer.FileStorageManager.volume_set_note') + def test_volume_set_note(self, set_note): + set_note.return_value = True + + result = self.run_command(['file', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("successfully!", result.output) + + @mock.patch('SoftLayer.FileStorageManager.volume_set_note') + def test_volume_not_set_note(self, set_note): + set_note.return_value = False + + result = self.run_command(['file', 'volume-set-note', '102', '--note=testing']) + + self.assert_no_fail(result) + self.assertIn("Note could not be set!", result.output) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 1ba644236..19d2d914c 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -111,7 +111,8 @@ def test_get_block_volume_details(self): 'replicationPartners[id,username,' \ 'serviceResourceBackendIpAddress,' \ 'serviceResource[datacenter[name]],' \ - 'replicationSchedule[type[keyname]]]' + 'replicationSchedule[type[keyname]]],' \ + 'notes' self.assert_called_with( 'SoftLayer_Network_Storage', diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 6df2b8721..3a8b4b9c8 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -145,7 +145,8 @@ def test_get_file_volume_details(self): 'replicationPartners[id,username,'\ 'serviceResourceBackendIpAddress,'\ 'serviceResource[datacenter[name]],'\ - 'replicationSchedule[type[keyname]]]' + 'replicationSchedule[type[keyname]]],' \ + 'notes' self.assert_called_with( 'SoftLayer_Network_Storage', From 16e17e14711706f9dba7fda21f553c5de920c681 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 25 Aug 2020 19:31:49 -0400 Subject: [PATCH 0446/1582] #1322 add volume-set-note docs --- SoftLayer/CLI/block/list.py | 8 ++++++-- SoftLayer/CLI/file/list.py | 8 ++++++-- docs/cli/block.rst | 4 ++++ docs/cli/file.rst | 4 ++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 7019166b8..6df25e13e 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -79,7 +79,7 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby - reduce_notes(block_volumes) + _reduce_notes(block_volumes) for block_volume in block_volumes: table.add_row([value or formatting.blank() @@ -88,7 +88,11 @@ def cli(env, sortby, columns, datacenter, username, storage_type): env.fout(table) -def reduce_notes(block_volumes): +def _reduce_notes(block_volumes): + """Reduces the size of the notes in a volume list. + + :param block_volumes: An list of block volumes + """ for block_volume in block_volumes: if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 731d0ee1c..8bb782884 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -77,7 +77,7 @@ def cli(env, sortby, columns, datacenter, username, storage_type): table = formatting.Table(columns.columns) table.sortby = sortby - reduce_notes(file_volumes) + _reduce_notes(file_volumes) for file_volume in file_volumes: table.add_row([value or formatting.blank() @@ -86,7 +86,11 @@ def cli(env, sortby, columns, datacenter, username, storage_type): env.fout(table) -def reduce_notes(file_volumes): +def _reduce_notes(file_volumes): + """Reduces the size of the notes in a volume list. + + :param file_volumes: An list of file volumes + """ for file_volume in file_volumes: if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 8b31d5a99..10ec8e6a6 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -138,3 +138,7 @@ Block Commands .. click:: SoftLayer.CLI.block.convert:cli :prog: block volume-convert :show-nested: + +.. click:: SoftLayer.CLI.block.set_note:cli + :prog: block volume-set-note + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 52dad83a4..5af9b65cc 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -117,4 +117,8 @@ File Commands .. click:: SoftLayer.CLI.file.convert:cli :prog: file volume-convert + :show-nested: + +.. click:: SoftLayer.CLI.file.set_note:cli + :prog: file volume-set-note :show-nested: \ No newline at end of file From 13cca31f5edad81ad258c91c04fdd1580a563072 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 25 Aug 2020 19:58:12 -0400 Subject: [PATCH 0447/1582] Refactor hardware create-options. --- SoftLayer/CLI/hardware/create_options.py | 67 +++++++++++------------- tests/CLI/modules/server_tests.py | 13 ++--- 2 files changed, 34 insertions(+), 46 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index f3a3dec14..7bd5ebcaf 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -4,17 +4,17 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import hardware @click.command() -@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') -@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' - 'keyName e.g. AMSTERDAM02') +@click.argument('location', required=False) +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' + 'to list the Item Prices by location, add it to the ' + '--prices option, e.g. --prices AMSTERDAM02') @environment.pass_env -def cli(env, prices, location): +def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) @@ -30,18 +30,14 @@ def cli(env, prices, location): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if location and prices: - raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") - if prices: _preset_prices_table(options['sizes'], tables) _os_prices_table(options['operating_systems'], tables) _port_speed_prices_table(options['port_speeds'], tables) _extras_prices_table(options['extras'], tables) - elif location: - _preset_prices_table(options['sizes'], tables) - location_prices = hardware_manager.get_hardware_item_prices(location) - _location_item_prices(location_prices, tables) + if location: + location_prices = hardware_manager.get_hardware_item_prices(location) + _location_item_prices(location_prices, tables) else: # Presets preset_table = formatting.Table(['Size', 'Value'], title="Sizes") @@ -98,20 +94,20 @@ def _os_prices_table(operating_systems, tables): :param [] operating_systems: List of Hardware Server operating systems. :param tables: Table formatting. """ - os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'capacityRestrictionMaximum', - 'capacityRestrictionMinimum', 'capacityRestrictionType'], + os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], title="Operating Systems Prices") os_prices_table.sortby = 'OS Key' os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') os_prices_table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType')]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(os_prices_table) @@ -121,20 +117,20 @@ def _port_speed_prices_table(port_speeds, tables): :param [] port_speeds: List of Hardware Server Port Speeds. :param tables: Table formatting. """ - port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType'], title="Network Options Prices") + port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], + title="Network Options Prices") port_speed_prices_table.sortby = 'Speed' port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') port_speed_prices_table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType')]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(port_speed_prices_table) @@ -144,20 +140,19 @@ def _extras_prices_table(extras, tables): :param [] extras: List of Hardware Server Extras. :param tables: Table formatting. """ - extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType'], title="Extras Prices") + extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], + title="Extras Prices") extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') extras_prices_table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(extras_prices_table) @@ -179,18 +174,16 @@ def _location_item_prices(location_prices, tables): :param price: Hardware Server price. :param string item: Hardware Server price data. """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', - 'capacityRestrictionMaximum', 'capacityRestrictionMinimum', - 'capacityRestrictionType']) + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') location_prices_table.add_row( [price['item']['keyName'], price['id'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 587124a96..9b235af12 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -364,17 +364,18 @@ def test_create_options(self): self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') def test_create_options_prices(self): - result = self.run_command(['server', 'create-options', '--prices=true']) + result = self.run_command(['server', 'create-options', '--prices']) self.assert_no_fail(result) output = json.loads(result.output) + print(output[3][0]) self.assertEqual(output[1][0]['Hourly'], 1.18) self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') - self.assertEqual(output[3][0]['capacityRestrictionMaximum'], '-') + self.assertEqual(output[3][0]['Monthly'], '0') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') def test_create_options_location(self): - result = self.run_command(['server', 'create-options', '--location=AMSTERDAM02']) + result = self.run_command(['server', 'create-options', '--prices', 'AMSTERDAM02']) self.assert_no_fail(result) output = json.loads(result.output) @@ -383,12 +384,6 @@ def test_create_options_location(self): self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') - def test_create_options_prices_location(self): - result = self.run_command(['server', 'create-options', '--prices=true', '--location=AMSTERDAM02']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): order_mock.return_value = { From 41d4ef5262bdf11e95f91a6e2d612c66bdb2b39f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 26 Aug 2020 16:22:57 -0500 Subject: [PATCH 0448/1582] #999 dns record-list can handle zones with a missing Host record (like SRV records). Improved the zone-list command a bit by adding the records count column --- SoftLayer/CLI/dns/record_list.py | 28 ++++++++++------------------ SoftLayer/CLI/dns/zone_list.py | 20 +++++++++++++------- SoftLayer/managers/dns.py | 20 +++++++++----------- tests/CLI/modules/dns_tests.py | 28 +++++++++++++++++++++++----- 4 files changed, 55 insertions(+), 41 deletions(-) diff --git a/SoftLayer/CLI/dns/record_list.py b/SoftLayer/CLI/dns/record_list.py index 4e46cb80c..4861582c9 100644 --- a/SoftLayer/CLI/dns/record_list.py +++ b/SoftLayer/CLI/dns/record_list.py @@ -14,36 +14,28 @@ @click.argument('zone') @click.option('--data', help='Record data, such as an IP address') @click.option('--record', help='Host record, such as www') -@click.option('--ttl', - type=click.INT, +@click.option('--ttl', type=click.INT, help='TTL value in seconds, such as 86400') -@click.option('--type', help='Record type, such as A or CNAME') +@click.option('--type', 'record_type', help='Record type, such as A or CNAME') @environment.pass_env -def cli(env, zone, data, record, ttl, type): +def cli(env, zone, data, record, ttl, record_type): """List all records in a zone.""" manager = SoftLayer.DNSManager(env.client) table = formatting.Table(['id', 'record', 'type', 'ttl', 'data']) - - table.align['ttl'] = 'l' - table.align['record'] = 'r' - table.align['data'] = 'l' + table.align = 'l' zone_id = helpers.resolve_id(manager.resolve_ids, zone, name='zone') - records = manager.get_records(zone_id, - record_type=type, - host=record, - ttl=ttl, - data=data) + records = manager.get_records(zone_id, record_type=record_type, host=record, ttl=ttl, data=data) for the_record in records: table.add_row([ - the_record['id'], - the_record['host'], - the_record['type'].upper(), - the_record['ttl'], - the_record['data'] + the_record.get('id'), + the_record.get('host', ''), + the_record.get('type').upper(), + the_record.get('ttl'), + the_record.get('data') ]) env.fout(table) diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py index f555677c7..a57d19c09 100644 --- a/SoftLayer/CLI/dns/zone_list.py +++ b/SoftLayer/CLI/dns/zone_list.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.utils import clean_time @click.command() @@ -14,17 +15,22 @@ def cli(env): """List all zones.""" manager = SoftLayer.DNSManager(env.client) - zones = manager.list_zones() - table = formatting.Table(['id', 'zone', 'serial', 'updated']) - table.align['serial'] = 'c' - table.align['updated'] = 'c' - + objectMask = "mask[id,name,serial,updateDate,resourceRecordCount]" + zones = manager.list_zones(mask=objectMask) + table = formatting.Table(['id', 'zone', 'serial', 'updated', 'records']) + table.align = 'l' for zone in zones: + zone_serial = str(zone.get('serial')) + zone_date = zone.get('updateDate', None) + if zone_date is None: + # The serial is just YYYYMMDD##, and since there is no createDate, just format it like it was one. + zone_date = "{}-{}-{}T00:00:00-06:00".format(zone_serial[0:4], zone_serial[4:6], zone_serial[6:8]) table.add_row([ zone['id'], zone['name'], - zone['serial'], - zone['updateDate'], + zone_serial, + clean_time(zone_date), + zone.get('resourceRecordCount', 0) ]) env.fout(table) diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 1e89ec9cf..332e96518 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -39,7 +39,10 @@ def list_zones(self, **kwargs): :returns: A list of dictionaries representing the matching zones. """ - return self.client['Account'].getDomains(**kwargs) + if kwargs.get('iter') is None: + kwargs['iter'] = True + return self.client.call('SoftLayer_Account', 'getDomains', **kwargs) + # return self.client['Account'].getDomains(**kwargs) def get_zone(self, zone_id, records=True): """Get a zone and its records. @@ -181,8 +184,7 @@ def get_record(self, record_id): """ return self.record.getObject(id=record_id) - def get_records(self, zone_id, ttl=None, data=None, host=None, - record_type=None): + def get_records(self, zone_id, ttl=None, data=None, host=None, record_type=None): """List, and optionally filter, records within a zone. :param zone: the zone name in which to search. @@ -191,8 +193,7 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, :param str host: record's host :param str record_type: the type of record - :returns: A list of dictionaries representing the matching records - within the specified zone. + :returns: A list of dictionaries representing the matching records within the specified zone. """ _filter = utils.NestedDict() @@ -208,12 +209,9 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, if record_type: _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) - results = self.service.getResourceRecords( - id=zone_id, - mask='id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson', - filter=_filter.to_dict(), - ) - + objectMask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' + results = self.client.call('SoftLayer_Dns_Domain', 'getResourceRecords', id=zone_id, + mask=objectMask, filter=_filter.to_dict(), iter=True) return results def edit_record(self, record): diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 3c1329b39..525e6bc7f 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -54,11 +54,8 @@ def test_list_zones(self): result = self.run_command(['dns', 'zone-list']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'serial': 2014030728, - 'updated': '2014-03-07T13:52:31-06:00', - 'id': 12345, - 'zone': 'example.com'}]) + actual_output = json.loads(result.output) + self.assertEqual(actual_output[0]['zone'], 'example.com') def test_list_records(self): result = self.run_command(['dns', 'record-list', '1234']) @@ -261,3 +258,24 @@ def test_import_zone(self): self.assertEqual(call.args[0], expected_call) self.assertIn("Finished", result.output) + + def test_issues_999(self): + """Makes sure certain zones with a None host record are pritable""" + + # SRV records can have a None `host` record, or just a plain missing one. + fake_records = [ + { + 'data': '1.2.3.4', + 'id': 137416416, + 'ttl': 900, + 'type': 'srv' + } + ] + record_mock = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') + record_mock.return_value = fake_records + result = self.run_command(['dns', 'record-list', '1234']) + + self.assert_no_fail(result) + actual_output = json.loads(result.output)[0] + self.assertEqual(actual_output['id'], 137416416) + self.assertEqual(actual_output['record'], '') \ No newline at end of file From dc6abfcaf69eabee9051e01e6f58e0d13050b5b6 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Aug 2020 13:08:14 -0500 Subject: [PATCH 0449/1582] tox and style fixes --- SoftLayer/CLI/dns/zone_list.py | 4 +- .../SoftLayer_Dns_Domain_ResourceRecord.py | 1 + SoftLayer/managers/dns.py | 4 +- tests/CLI/modules/dns_tests.py | 18 ++++- tests/managers/dns_tests.py | 73 +++++++------------ 5 files changed, 47 insertions(+), 53 deletions(-) diff --git a/SoftLayer/CLI/dns/zone_list.py b/SoftLayer/CLI/dns/zone_list.py index a57d19c09..7e0b5fe9e 100644 --- a/SoftLayer/CLI/dns/zone_list.py +++ b/SoftLayer/CLI/dns/zone_list.py @@ -15,8 +15,8 @@ def cli(env): """List all zones.""" manager = SoftLayer.DNSManager(env.client) - objectMask = "mask[id,name,serial,updateDate,resourceRecordCount]" - zones = manager.list_zones(mask=objectMask) + object_mask = "mask[id,name,serial,updateDate,resourceRecordCount]" + zones = manager.list_zones(mask=object_mask) table = formatting.Table(['id', 'zone', 'serial', 'updated', 'records']) table.align = 'l' for zone in zones: diff --git a/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py b/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py index ce85e95d8..e098e159f 100644 --- a/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py +++ b/SoftLayer/fixtures/SoftLayer_Dns_Domain_ResourceRecord.py @@ -1,3 +1,4 @@ createObject = {'name': 'example.com'} deleteObject = True editObject = True +getObject = {'id': 12345, 'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'} diff --git a/SoftLayer/managers/dns.py b/SoftLayer/managers/dns.py index 332e96518..6484fd7d9 100644 --- a/SoftLayer/managers/dns.py +++ b/SoftLayer/managers/dns.py @@ -209,9 +209,9 @@ def get_records(self, zone_id, ttl=None, data=None, host=None, record_type=None) if record_type: _filter['resourceRecords']['type'] = utils.query_filter(record_type.lower()) - objectMask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' + object_mask = 'id,expire,domainId,host,minimum,refresh,retry,mxPriority,ttl,type,data,responsiblePerson' results = self.client.call('SoftLayer_Dns_Domain', 'getResourceRecords', id=zone_id, - mask=objectMask, filter=_filter.to_dict(), iter=True) + mask=object_mask, filter=_filter.to_dict(), iter=True) return results def edit_record(self, record): diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 525e6bc7f..90012de3f 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -278,4 +278,20 @@ def test_issues_999(self): self.assert_no_fail(result) actual_output = json.loads(result.output)[0] self.assertEqual(actual_output['id'], 137416416) - self.assertEqual(actual_output['record'], '') \ No newline at end of file + self.assertEqual(actual_output['record'], '') + + def test_list_zones_no_update(self): + fake_zones = [ + { + 'name': 'example.com', + 'id': 12345, + 'serial': 2014030728, + 'updateDate': None} + ] + domains_mock = self.set_mock('SoftLayer_Account', 'getDomains') + domains_mock.return_value = fake_zones + result = self.run_command(['dns', 'zone-list']) + + self.assert_no_fail(result) + actual_output = json.loads(result.output) + self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') diff --git a/tests/managers/dns_tests.py b/tests/managers/dns_tests.py index 6b32af918..0c7c1cade 100644 --- a/tests/managers/dns_tests.py +++ b/tests/managers/dns_tests.py @@ -25,24 +25,19 @@ def test_get_zone(self): res = self.dns_client.get_zone(12345) self.assertEqual(res, fixtures.SoftLayer_Dns_Domain.getObject) - self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', - identifier=12345, - mask='mask[resourceRecords]') + self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', identifier=12345, mask='mask[resourceRecords]') def test_get_zone_without_records(self): self.dns_client.get_zone(12345, records=False) - self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', - identifier=12345, - mask=None) + self.assert_called_with('SoftLayer_Dns_Domain', 'getObject', identifier=12345, mask=None) def test_resolve_zone_name(self): res = self.dns_client._get_zone_id_from_name('example.com') self.assertEqual([12345], res) _filter = {"domains": {"name": {"operation": "_= example.com"}}} - self.assert_called_with('SoftLayer_Account', 'getDomains', - filter=_filter) + self.assert_called_with('SoftLayer_Account', 'getDomains', filter=_filter) def test_resolve_zone_name_no_matches(self): mock = self.set_mock('SoftLayer_Account', 'getDomains') @@ -52,8 +47,7 @@ def test_resolve_zone_name_no_matches(self): self.assertEqual([], res) _filter = {"domains": {"name": {"operation": "_= example.com"}}} - self.assert_called_with('SoftLayer_Account', 'getDomains', - filter=_filter) + self.assert_called_with('SoftLayer_Account', 'getDomains', filter=_filter) def test_create_zone(self): res = self.dns_client.create_zone('example.com', serial='2014110201') @@ -61,27 +55,22 @@ def test_create_zone(self): args = ({'serial': '2014110201', 'name': 'example.com', 'resourceRecords': {}},) - self.assert_called_with('SoftLayer_Dns_Domain', 'createObject', - args=args) + self.assert_called_with('SoftLayer_Dns_Domain', 'createObject', args=args) self.assertEqual(res, {'name': 'example.com'}) def test_delete_zone(self): self.dns_client.delete_zone(1) - self.assert_called_with('SoftLayer_Dns_Domain', 'deleteObject', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain', 'deleteObject', identifier=1) def test_edit_zone(self): self.dns_client.edit_zone('example.com') - self.assert_called_with('SoftLayer_Dns_Domain', 'editObject', - args=('example.com',)) + self.assert_called_with('SoftLayer_Dns_Domain', 'editObject', args=('example.com',)) def test_create_record(self): - res = self.dns_client.create_record(1, 'test', 'TXT', 'testing', - ttl=1200) + res = self.dns_client.create_record(1, 'test', 'TXT', 'testing', ttl=1200) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'domainId': 1, 'ttl': 1200, @@ -94,8 +83,7 @@ def test_create_record(self): def test_create_record_mx(self): res = self.dns_client.create_record_mx(1, 'test', 'testing', ttl=1200, priority=21) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'domainId': 1, 'ttl': 1200, @@ -110,8 +98,7 @@ def test_create_record_srv(self): res = self.dns_client.create_record_srv(1, 'record', 'test_data', 'SLS', 8080, 'foobar', ttl=1200, priority=21, weight=15) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', args=({ 'complexType': 'SoftLayer_Dns_Domain_ResourceRecord_SrvType', 'domainId': 1, @@ -130,14 +117,8 @@ def test_create_record_srv(self): def test_create_record_ptr(self): res = self.dns_client.create_record_ptr('test', 'testing', ttl=1200) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'createObject', - args=({ - 'ttl': 1200, - 'host': 'test', - 'type': 'PTR', - 'data': 'testing' - },)) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'createObject', + args=({'ttl': 1200, 'host': 'test', 'type': 'PTR', 'data': 'testing'},)) self.assertEqual(res, {'name': 'example.com'}) def test_generate_create_dict(self): @@ -161,27 +142,21 @@ def test_generate_create_dict(self): def test_delete_record(self): self.dns_client.delete_record(1) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'deleteObject', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'deleteObject', identifier=1) def test_edit_record(self): self.dns_client.edit_record({'id': 1, 'name': 'test', 'ttl': '1800'}) - self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', - 'editObject', - args=({'id': 1, - 'name': 'test', - 'ttl': '1800'},), + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'editObject', + args=({'id': 1, 'name': 'test', 'ttl': '1800'},), identifier=1) def test_dump_zone(self): self.dns_client.dump_zone(1) - self.assert_called_with('SoftLayer_Dns_Domain', 'getZoneFileContents', - identifier=1) + self.assert_called_with('SoftLayer_Dns_Domain', 'getZoneFileContents', identifier=1) - def test_get_record(self): + def test_get_records(self): # maybe valid domain, but no records matching mock = self.set_mock('SoftLayer_Dns_Domain', 'getResourceRecords') mock.return_value = [] @@ -190,11 +165,7 @@ def test_get_record(self): mock.reset_mock() records = fixtures.SoftLayer_Dns_Domain.getResourceRecords mock.return_value = [records[0]] - self.dns_client.get_records(12345, - record_type='a', - host='hostname', - data='a', - ttl='86400') + self.dns_client.get_records(12345, record_type='a', host='hostname', data='a', ttl='86400') _filter = {'resourceRecords': {'type': {'operation': '_= a'}, 'host': {'operation': '_= hostname'}, @@ -203,3 +174,9 @@ def test_get_record(self): self.assert_called_with('SoftLayer_Dns_Domain', 'getResourceRecords', identifier=12345, filter=_filter) + + def test_get_record(self): + record_id = 1234 + record = self.dns_client.get_record(record_id) + self.assertEqual(record['id'], 12345) + self.assert_called_with('SoftLayer_Dns_Domain_ResourceRecord', 'getObject', identifier=record_id) From 78ce548873e227d4fe8be801165369917dc274bd Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 27 Aug 2020 14:49:59 -0500 Subject: [PATCH 0450/1582] fixed a unit test for older versions of python --- tests/CLI/modules/dns_tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 90012de3f..82403d1a9 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -6,6 +6,7 @@ """ import json import os.path +import sys import mock @@ -281,6 +282,7 @@ def test_issues_999(self): self.assertEqual(actual_output['record'], '') def test_list_zones_no_update(self): + pyversion = sys.version_info fake_zones = [ { 'name': 'example.com', @@ -294,4 +296,7 @@ def test_list_zones_no_update(self): self.assert_no_fail(result) actual_output = json.loads(result.output) - self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') + if pyversion.major >= 3 and pyversion.minor >= 7: + self.assertEqual(actual_output[0]['updated'], '2014-03-07 00:00') + else: + self.assertEqual(actual_output[0]['updated'], '2014-03-07T00:00:00-06:00') From 7c8669208d56bb3f32640bdeb006848a2ed6312d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 27 Aug 2020 16:34:54 -0400 Subject: [PATCH 0451/1582] Refactor order item-list. --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/CLI/order/item_list.py | 76 ++++++++++++------------ tests/CLI/modules/order_tests.py | 22 +++---- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 7bd5ebcaf..71a988451 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -12,7 +12,7 @@ @click.argument('location', required=False) @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' 'to list the Item Prices by location, add it to the ' - '--prices option, e.g. --prices AMSTERDAM02') + '--prices option using location KeyName, e.g. --prices AMSTERDAM02') @environment.pass_env def cli(env, prices, location=None): """Server order options for a given chassis.""" diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index 97e9c985e..d22be8b56 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -3,25 +3,25 @@ import click from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting from SoftLayer.managers import ordering from SoftLayer.utils import lookup COLUMNS = ['category', 'keyName', 'description', 'priceId'] -COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] -COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'CRMax', 'CRMim', 'CRType'] +COLUMNS_ITEM_PRICES = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] +COLUMNS_ITEM_PRICES_LOCATION = ['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction'] @click.command() +@click.argument('location', required=False, nargs=-1, type=click.UNPROCESSED) @click.argument('package_keyname') @click.option('--keyword', help="A word (or string) used to filter item names.") @click.option('--category', help="Category code to filter items by") -@click.option('--prices', '-p', default=False, help='Filter Item Prices, prices(DEFAULT False)') -@click.option('--location', '-l', type=click.STRING, help='To filter the item prices by location, enter the Location ' - 'keyName e.g. AMSTERDAM02') +@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' + 'Item Prices by location, add it to the --prices option using ' + 'location KeyName, e.g. --prices AMSTERDAM02') @environment.pass_env -def cli(env, package_keyname, keyword, category, prices, location): +def cli(env, location, package_keyname, keyword, category, prices): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -42,8 +42,7 @@ def cli(env, package_keyname, keyword, category, prices, location): """ manager = ordering.OrderingManager(env.client) - if location and prices: - raise exceptions.CLIAbort("Please select --prices or --location to get the prices, not both") + tables = [] _filter = {'items': {}} if keyword: @@ -56,22 +55,18 @@ def cli(env, package_keyname, keyword, category, prices, location): categories = sorted_items.keys() if prices: - table = formatting.Table(COLUMNS_ITEM_PRICES, title="CRMax = CapacityRestrictionMaximum, " - "CRMin = CapacityRestrictionMinimum, " - "CRType = CapacityRestrictionType") - table = _item_list_prices(categories, sorted_items, table) - elif location: - table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="CRMax = CapacityRestrictionMaximum, " - "CRMin = CapacityRestrictionMinimum, " - "CRType = CapacityRestrictionType") - location_prices = manager.get_item_prices_by_location(location, package_keyname) - table = _location_item_prices(location_prices, table) + _item_list_prices(categories, sorted_items, tables) + if location: + location = location[0] + location_prices = manager.get_item_prices_by_location(location, package_keyname) + _location_item_prices(location_prices, location, tables) else: - table = formatting.Table(COLUMNS) + table_items_detail = formatting.Table(COLUMNS) for catname in sorted(categories): for item in sorted_items[catname]: - table.add_row([catname, item['keyName'], item['description'], get_price(item)]) - env.fout(table) + table_items_detail.add_row([catname, item['keyName'], item['description'], get_price(item)]) + tables.append(table_items_detail) + env.fout(formatting.listing(tables, separator='\n')) def sort_items(items): @@ -96,19 +91,21 @@ def get_price(item): return 0 -def _item_list_prices(categories, sorted_items, table): +def _item_list_prices(categories, sorted_items, tables): """Add the item prices cost and capacity restriction to the table""" + table_prices = formatting.Table(COLUMNS_ITEM_PRICES) for catname in sorted(categories): for item in sorted_items[catname]: for price in item['prices']: if not price.get('locationGroupId'): - table.add_row([item['keyName'], price['id'], - get_item_price_data(price, 'hourlyRecurringFee'), - get_item_price_data(price, 'recurringFee'), - get_item_price_data(price, 'capacityRestrictionMaximum'), - get_item_price_data(price, 'capacityRestrictionMinimum'), - get_item_price_data(price, 'capacityRestrictionType')]) - return table + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + table_prices.add_row([item['keyName'], price['id'], + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(table_prices) def get_item_price_data(price, item_attribute): @@ -119,24 +116,25 @@ def get_item_price_data(price, item_attribute): return result -def _location_item_prices(location_prices, table): +def _location_item_prices(location_prices, location, tables): """Get a specific data from HS price. :param price: Hardware Server price. :param string item: Hardware Server price data. """ - table.sortby = 'keyName' - table.align = 'l' + location_prices_table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="Item Prices for %s" % location) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' for price in location_prices: - table.add_row( + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( [price['item']['keyName'], price['id'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), - _get_price_data(price, 'capacityRestrictionMaximum'), - _get_price_data(price, 'capacityRestrictionMinimum'), - _get_price_data(price, 'capacityRestrictionType') - ]) - return table + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(location_prices_table) def _get_price_data(price, item): diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 7b5a1dbec..7cc0ed611 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -46,31 +46,25 @@ def test_item_list(self): self.assertIn('item2', result.output) def test_item_list_prices(self): - result = self.run_command(['order', 'item-list', '--prices=true', 'package']) + result = self.run_command(['order', 'item-list', '--prices', 'package']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0]['Hourly'], 0.0) - self.assertEqual(output[1]['CRMim'], '-') - self.assertEqual(output[1]['keyName'], 'KeyName015') + self.assertEqual(output[0][0]['priceId'], 1007) + self.assertEqual(output[0][1]['Restriction'], '- - - -') + self.assertEqual(output[0][1]['keyName'], 'KeyName015') self.assert_called_with('SoftLayer_Product_Package', 'getItems') def test_item_list_location(self): - result = self.run_command(['order', 'item-list', '--location=AMSTERDAM02', 'package']) + result = self.run_command(['order', 'item-list', '--prices', 'AMSTERDAM02', 'package']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0]['Hourly'], '.093') - self.assertEqual(output[1]['keyName'], 'GUEST_DISK_100_GB_LOCAL_3') - self.assertEqual(output[1]['CRMax'], '-') + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['Monthly'], '-') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') - def test_item_list_prices_location(self): - result = self.run_command(['order', 'item-list', '--prices=true', '--location=AMSTERDAM02', 'package']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - def test_package_list(self): p_mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') p_mock.return_value = _get_all_packages() From cd8dde130bcc0d123e7e3cb10d13e8ad0feca479 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 27 Aug 2020 17:25:14 -0400 Subject: [PATCH 0452/1582] Fix tox test. --- 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 7cc0ed611..13ccb0f80 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -62,7 +62,7 @@ def test_item_list_location(self): output = json.loads(result.output) self.assertEqual(output[0][0]['Hourly'], 0.0) self.assertEqual(output[0][1]['keyName'], 'KeyName015') - self.assertEqual(output[0][1]['Monthly'], '-') + self.assertEqual(output[0][1]['priceId'], 1144) self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') def test_package_list(self): From 02a2488bfe47b88554781a96637bc483c4a4f545 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 28 Aug 2020 12:54:06 -0400 Subject: [PATCH 0453/1582] Refactor hw create-options size decimals. --- SoftLayer/CLI/hardware/create_options.py | 3 ++- tests/CLI/modules/server_tests.py | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 71a988451..1ae06a9be 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -84,7 +84,8 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - preset_prices_table.add_row([size['name'], size['key'], size['hourlyRecurringFee'], size['recurringFee']]) + preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b235af12..ebf3dc1c0 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -368,8 +368,7 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) - print(output[3][0]) - self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') self.assertEqual(output[3][0]['Monthly'], '0') self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') @@ -379,8 +378,8 @@ def test_create_options_location(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], 780.0) - self.assertEqual(output[1][0]['Hourly'], 1.18) + self.assertEqual(output[1][0]['Monthly'], "%.4f" % 780.0) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') From f84b4b66e993d24d66510a94667650d92e141c32 Mon Sep 17 00:00:00 2001 From: try Date: Tue, 1 Sep 2020 22:00:23 +0530 Subject: [PATCH 0454/1582] Please review slcli --- SoftLayer/CLI/block/refresh.py | 2 +- SoftLayer/CLI/file/refresh.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Network_Storage.py | 4 ++-- SoftLayer/managers/storage.py | 4 ++-- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- tests/managers/block_tests.py | 8 ++++---- tests/managers/file_tests.py | 8 ++++---- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index aebc5e668..0c94ae67f 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -13,7 +13,7 @@ def cli(env, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) - resp = block_manager.refresh_dupe(volume_id, snapshot_id) #remove dep_ in refresh_dep_dupe + resp = block_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py index c566dac91..765e82730 100644 --- a/SoftLayer/CLI/file/refresh.py +++ b/SoftLayer/CLI/file/refresh.py @@ -11,8 +11,8 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a dependent duplicate volume with a snapshot from its parent.""" + """"Refresh a duplicate volume with a snapshot from its parent.""" file_manager = SoftLayer.FileStorageManager(env.client) - resp = file_manager.refresh_dep_dupe(volume_id, snapshot_id) + resp = file_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 6fc9ed3e3..8f9de6dc2 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -234,8 +234,8 @@ 'provisionedCount': 100 } -refreshDuplicate = { #remove Dependent from refreshDependentDuplicate - 'DependentDuplicate': 1 +refreshDuplicate = { + 'dependentDuplicate': 1 } convertCloneDependentToIndependent = { diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 9f750272c..cd582701d 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -415,13 +415,13 @@ def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id) - def refresh_dupe(self, volume_id, snapshot_id): #remove dep + def refresh_dupe(self, volume_id, snapshot_id): """"Refresh a duplicate volume with a snapshot from its parent. :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) #remove Dependent + return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 3aeaa0dc6..672bd3922 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -717,7 +717,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['block', 'volume-limits']) self.assert_no_fail(result) - def test_dupe_refresh(self): #remove _dep in test_dep_dupe_refresh + def test_dupe_refresh(self): result = self.run_command(['block', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1d64f54ae..f29809646 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -696,7 +696,7 @@ def test_volume_limit(self, list_mock): result = self.run_command(['file', 'volume-limits']) self.assert_no_fail(result) - def test_dep_dupe_refresh(self): + def test_dupe_refresh(self): result = self.run_command(['file', 'volume-refresh', '102', '103']) self.assert_no_fail(result) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index cd99f6221..6d155345e 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -1043,13 +1043,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getIscsiNetworkStorage') self.assertEqual([], result) - def test_refresh_block_dupe(self): #remove dep in block_depdupe - result = self.block.refresh_dupe(123, snapshot_id=321) #remove dep in refresh_dep_dupe - self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) #remove Dependent in refreshDependentDuplicate + def test_refresh_block_dupe(self): + result = self.block.refresh_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDuplicate', #remove Dependent in refreshDependentDuplicate + 'refreshDuplicate', identifier=123 ) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 6df2b8721..141ab9602 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -892,13 +892,13 @@ def test_get_ids_from_username_empty(self): self.assert_called_with('SoftLayer_Account', 'getNasNetworkStorage') self.assertEqual([], result) - def test_refresh_file_depdupe(self): - result = self.file.refresh_dep_dupe(123, snapshot_id=321) - self.assertEqual(SoftLayer_Network_Storage.refreshDependentDuplicate, result) + def test_refresh_file_dupe(self): + result = self.file.refresh_dupe(123, snapshot_id=321) + self.assertEqual(SoftLayer_Network_Storage.refreshDuplicate, result) self.assert_called_with( 'SoftLayer_Network_Storage', - 'refreshDependentDuplicate', + 'refreshDuplicate', identifier=123 ) From 93356bb8d9ed18c1195116fe243364bc75f68b4f Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 1 Sep 2020 15:58:36 -0400 Subject: [PATCH 0455/1582] refactor vsi create-option --- SoftLayer/CLI/virt/create_options.py | 275 +++++------------- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 109 ++++++- SoftLayer/managers/vs.py | 91 +++++- tests/CLI/modules/vs/vs_tests.py | 14 +- tests/managers/vs/vs_tests.py | 7 +- 5 files changed, 269 insertions(+), 227 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 693de74a2..64a2c71a3 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -6,213 +6,92 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer import utils @click.command(short_help="Get options to use for creating virtual servers.") +@click.option('--vsi-type', required=False, type=click.Choice(['TRANSIENT', 'SUSPEND']), + help="Billing rate") @environment.pass_env -def cli(env): +def cli(env, vsi_type): """Virtual server order options.""" + if vsi_type is None: + vsi_type = 'PUBLIC' + vsi_type = vsi_type + '_CLOUD_SERVER' vsi = SoftLayer.VSManager(env.client) - options = vsi.get_create_options() - - tables = [ - _get_datacenter_table(options), - _get_flavors_table(options), - _get_cpu_table(options), - _get_memory_table(options), - _get_os_table(options), - _get_disk_table(options), - _get_network_table(options), - ] - - env.fout(formatting.listing(tables, separator='\n')) + options = vsi.get_create_options(vsi_type) + tables = [] -def _get_datacenter_table(create_options): - datacenters = [dc['template']['datacenter']['name'] - for dc in create_options['datacenters']] + # Datacenters + dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") + dc_table.sortby = 'Value' + dc_table.align = 'l' - datacenters = sorted(datacenters) + for location in options['locations']: + dc_table.add_row([location['name'], location['key']]) + tables.append(dc_table) - dc_table = formatting.Table(['datacenter'], title='Datacenters') - dc_table.sortby = 'datacenter' - dc_table.align = 'l' - for datacenter in datacenters: - dc_table.add_row([datacenter]) - return dc_table - - -def _get_flavors_table(create_options): - flavor_table = formatting.Table(['flavor', 'value'], title='Flavors') - flavor_table.sortby = 'flavor' - flavor_table.align = 'l' - grouping = { - 'balanced': {'key_starts_with': 'B1', 'flavors': []}, - 'balanced local - hdd': {'key_starts_with': 'BL1', 'flavors': []}, - 'balanced local - ssd': {'key_starts_with': 'BL2', 'flavors': []}, - 'compute': {'key_starts_with': 'C1', 'flavors': []}, - 'memory': {'key_starts_with': 'M1', 'flavors': []}, - 'GPU': {'key_starts_with': 'AC', 'flavors': []}, - 'transient': {'transient': True, 'flavors': []}, - } - - if create_options.get('flavors', None) is None: - return flavor_table - - for flavor_option in create_options['flavors']: - flavor_key_name = utils.lookup(flavor_option, 'flavor', 'keyName') - - for name, group in grouping.items(): - if utils.lookup(flavor_option, 'template', 'transientGuestFlag') is True: - if utils.lookup(group, 'transient') is True: - group['flavors'].append(flavor_key_name) - break - - elif utils.lookup(group, 'key_starts_with') is not None \ - and flavor_key_name.startswith(group['key_starts_with']): - group['flavors'].append(flavor_key_name) - break - - for name, group in grouping.items(): - if len(group['flavors']) > 0: - flavor_table.add_row(['{}'.format(name), - formatting.listing(group['flavors'], - separator='\n')]) - return flavor_table - - -def _get_cpu_table(create_options): - cpu_table = formatting.Table(['cpu', 'value'], title='CPUs') - cpu_table.sortby = 'cpu' - cpu_table.align = 'l' - standard_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if not x['template'].get('dedicatedAccountHostOnlyFlag', - False) - and not x['template'].get('dedicatedHost', None)] - ded_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if x['template'].get('dedicatedAccountHostOnlyFlag', False)] - ded_host_cpus = [int(x['template']['startCpus']) for x in create_options['processors'] - if x['template'].get('dedicatedHost', None)] - - standard_cpus = sorted(standard_cpus) - cpu_table.add_row(['standard', formatting.listing(standard_cpus, separator=',')]) - ded_cpus = sorted(ded_cpus) - cpu_table.add_row(['dedicated', formatting.listing(ded_cpus, separator=',')]) - ded_host_cpus = sorted(ded_host_cpus) - cpu_table.add_row(['dedicated host', formatting.listing(ded_host_cpus, separator=',')]) - return cpu_table - - -def _get_memory_table(create_options): - memory_table = formatting.Table(['memory', 'value'], title='Memories') - memory_table.sortby = 'memory' - memory_table.align = 'l' - memory = [int(m['template']['maxMemory']) for m in create_options['memory'] - if not m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - ded_host_memory = [int(m['template']['maxMemory']) for m in create_options['memory'] - if m['itemPrice'].get('dedicatedHostInstanceFlag', False)] - - memory = sorted(memory) - memory_table.add_row(['standard', - formatting.listing(memory, separator=',')]) - - ded_host_memory = sorted(ded_host_memory) - memory_table.add_row(['dedicated host', - formatting.listing(ded_host_memory, separator=',')]) - return memory_table - - -def _get_os_table(create_options): - os_table = formatting.Table(['KeyName', 'Description'], title='Operating Systems') - os_table.sortby = 'KeyName' + # Operation system + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' os_table.align = 'l' - op_sys = [] - for operating_system in create_options['operatingSystems']: - os_option = { - 'referenceCode': operating_system['template']['operatingSystemReferenceCode'], - 'description': operating_system['itemPrice']['item']['description'] - } - op_sys.append(os_option) - - for operating_system in op_sys: - os_table.add_row([ - operating_system['referenceCode'], - operating_system['description'] - ]) - return os_table - - -def _get_disk_table(create_options): - disk_table = formatting.Table(['disk', 'value'], title='Disks') - disk_table.sortby = 'disk' - disk_table.align = 'l' - local_disks = [x for x in create_options['blockDevices'] - if x['template'].get('localDiskFlag', False) - and not x['itemPrice'].get('dedicatedHostInstanceFlag', - False)] - - ded_host_local_disks = [x for x in create_options['blockDevices'] - if x['template'].get('localDiskFlag', False) - and x['itemPrice'].get('dedicatedHostInstanceFlag', - False)] - - san_disks = [x for x in create_options['blockDevices'] - if not x['template'].get('localDiskFlag', False)] - - def add_block_rows(disks, name): - """Add block rows to the table.""" - simple = {} - for disk in disks: - block = disk['template']['blockDevices'][0] - bid = block['device'] - - if bid not in simple: - simple[bid] = [] - - simple[bid].append(str(block['diskImage']['capacity'])) - - for label in sorted(simple): - disk_table.add_row(['%s disk(%s)' % (name, label), - formatting.listing(simple[label], - separator=',')]) - - add_block_rows(san_disks, 'san') - add_block_rows(local_disks, 'local') - add_block_rows(ded_host_local_disks, 'local (dedicated host)') - return disk_table - - -def _get_network_table(create_options): - network_table = formatting.Table(['network', 'value'], title='Network') - network_table.sortby = 'network' - network_table.align = 'l' - speeds = [] - ded_host_speeds = [] - for option in create_options['networkComponents']: - template = option.get('template', None) - price = option.get('itemPrice', None) - - if not template or not price \ - or not template.get('networkComponents', None): - continue - - if not template['networkComponents'][0] \ - or not template['networkComponents'][0].get('maxSpeed', None): - continue - - max_speed = str(template['networkComponents'][0]['maxSpeed']) - if price.get('dedicatedHostInstanceFlag', False) \ - and max_speed not in ded_host_speeds: - ded_host_speeds.append(max_speed) - elif max_speed not in speeds: - speeds.append(max_speed) - - speeds = sorted(speeds) - network_table.add_row(['nic', formatting.listing(speeds, separator=',')]) - - ded_host_speeds = sorted(ded_host_speeds) - network_table.add_row(['nic (dedicated host)', - formatting.listing(ded_host_speeds, separator=',')]) - return network_table + + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + flavors_table = formatting.Table(['flavor', 'Name'], title="Flavors") + flavors_table.sortby = 'Name' + flavors_table.align = 'l' + + for flavor in options['flavors']: + flavors_table.add_row([flavor['flavor']['keyName'], flavor['flavor']['name']]) + tables.append(flavors_table) + + # RAM + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' + ram_table.align = 'l' + + for ram in options['ram']: + ram_table.add_row([ram['name'], ram['key']]) + tables.append(ram_table) + + # Data base + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' + database_table.align = 'l' + + for database in options['database']: + database_table.add_row([database['name'], database['key']]) + tables.append(database_table) + + # Guest_core + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' + guest_core_table.align = 'l' + + for guest_core in options['guest_core']: + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + tables.append(guest_core_table) + + # Guest_core + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' + guest_disk_table.align = 'l' + + for guest_disk in options['guest_disk']: + guest_disk_table.add_row([guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + tables.append(guest_disk_table) + + # Port speed + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' + port_speed_table.align = 'l' + + for speed in options['port_speed']: + port_speed_table.add_row([speed['name'], speed['key']]) + tables.append(port_speed_table) + + env.fout(formatting.listing(tables, separator='\n')) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 3b9a24302..987fd12d7 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -68,12 +68,12 @@ }], 'tagReferences': [{'tag': {'name': 'production'}}], } - getCreateObjectOptions = { 'flavors': [ { 'flavor': { - 'keyName': 'B1_1X2X25' + 'keyName': 'B1_1X2X25', + 'name': 'B1-1X2X25' }, 'template': { 'supplementalCreateObjectOptions': { @@ -83,7 +83,8 @@ }, { 'flavor': { - 'keyName': 'B1_1X2X25_TRANSIENT' + 'keyName': 'B1_1X2X25_TRANSIENT', + 'name': 'B1-1X2X25_TRANSIENT' }, 'template': { 'supplementalCreateObjectOptions': { @@ -94,7 +95,8 @@ }, { 'flavor': { - 'keyName': 'B1_1X2X100' + 'keyName': 'B1_1X2X100', + 'name': 'B1-1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -104,7 +106,8 @@ }, { 'flavor': { - 'keyName': 'BL1_1X2X100' + 'keyName': 'BL1_1X2X100', + 'name': 'BL1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -114,7 +117,8 @@ }, { 'flavor': { - 'keyName': 'BL2_1X2X100' + 'keyName': 'BL2_1X2X100', + 'name': 'BL2-1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -124,7 +128,8 @@ }, { 'flavor': { - 'keyName': 'C1_1X2X25' + 'keyName': 'C1_1X2X25', + 'name': 'C1-1X2X25' }, 'template': { 'supplementalCreateObjectOptions': { @@ -134,7 +139,8 @@ }, { 'flavor': { - 'keyName': 'M1_1X2X100' + 'keyName': 'M1_1X2X100', + 'name': 'M1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -144,7 +150,8 @@ }, { 'flavor': { - 'keyName': 'AC1_1X2X100' + 'keyName': 'AC1_1X2X100', + 'name': 'AC1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -154,7 +161,8 @@ }, { 'flavor': { - 'keyName': 'ACL1_1X2X100' + 'keyName': 'ACL1_1X2X100', + 'name': 'ACL1_1X2X100' }, 'template': { 'supplementalCreateObjectOptions': { @@ -245,6 +253,12 @@ ], 'memory': [ { + "description": "1 GB ", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 4 + }, 'itemPrice': { 'item': {'description': '1 GB'}, 'hourlyRecurringFee': '.03', @@ -253,14 +267,27 @@ 'template': {'maxMemory': 1024} }, { - 'itemPrice': { - 'item': {'description': '2 GB'}, - 'hourlyRecurringFee': '.06', - 'recurringFee': '42' + "description": "2 GB ", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 5 }, + 'itemPrice': + { + 'item': {'description': '2 GB'}, + 'hourlyRecurringFee': '.06', + 'recurringFee': '42' + }, 'template': {'maxMemory': 2048} }, { + "description": "3 GB", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 65 + }, 'itemPrice': { 'item': {'description': '3 GB'}, 'hourlyRecurringFee': '.085', @@ -268,6 +295,12 @@ 'template': {'maxMemory': 3072} }, { + "description": "4 GB", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 35 + }, 'itemPrice': { 'item': {'description': '4 GB'}, 'hourlyRecurringFee': '.11', @@ -276,6 +309,12 @@ 'template': {'maxMemory': 4096} }, { + "description": "64 GB (Dedicated Host)", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 3 + }, 'itemPrice': { 'hourlyRecurringFee': '0', 'recurringFee': '0', @@ -289,6 +328,12 @@ } }, { + "description": "8 GB (Dedicated Host)", + "attributes": [], + "itemCategory": { + "categoryCode": "ram", + "id": 36 + }, 'itemPrice': { 'hourlyRecurringFee': '0', 'recurringFee': '0', @@ -423,6 +468,42 @@ {'template': {'datacenter': {'name': 'ams01'}}}, {'template': {'datacenter': {'name': 'dal05'}}}, ], + 'guest_disk': [{ + "description": "25 GB (SAN)", + "attributes": [ + { + "id": 197, + "attributeTypeKeyName": "SAN_DISK" + } + ], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 81 + }}, { + "description": "250 GB (SAN)", + "attributes": [ + { + "id": 198, + "attributeTypeKeyName": "SAN_DISK" + }], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 89 + }}], + 'guest_core': [{ + "description": "4 x 2.0 GHz or higher Cores (Dedicated)", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 80 + }}, + { + "description": "8 x 2.0 GHz or higher Cores", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 90 + }}] } getReverseDomainRecords = [{ diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 2a58d9b62..6c6fec747 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -249,7 +249,7 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self): + def get_create_options(self, vsi_type): """Retrieves the available options for creating a VS. :returns: A dictionary of creation options. @@ -260,7 +260,94 @@ def get_create_options(self): options = mgr.get_create_options() print(options) """ - return self.guest.getCreateObjectOptions() + + # TRANSIENT_CLOUD_SERVER + # SUSPEND_CLOUD_SERVER + package = self._get_package(vsi_type) + + # Locations + locations = [] + for region in package['regions']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) + + operating_systems = [] + database = [] + port_speeds = [] + guest_core = [] + local_disk = [] + ram = [] + for item in package['items']: + category = item['itemCategory']['categoryCode'] + # Operating systems + if category == 'os': + operating_systems.append({ + 'name': item['softwareDescription']['longDescription'], + 'key': item['keyName'], + 'referenceCode': item['softwareDescription']['referenceCode'] + }) + # database + elif category == 'database': + database.append({ + 'name': item['description'], + 'key': item['keyName'] + }) + + elif category == 'port_speed': + port_speeds.append({ + 'name': item['description'], + 'key': item['keyName'] + }) + + elif category == 'guest_core': + guest_core.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'] + }) + + elif category == 'ram': + ram.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'] + }) + + elif category.__contains__('guest_disk'): + local_disk.append({ + 'name': item['description'], + 'capacity': item['capacity'], + 'key': item['keyName'], + 'disk': category + }) + # Extras + + return { + 'locations': locations, + 'ram': ram, + 'database': database, + 'operating_systems': operating_systems, + 'guest_core': guest_core, + 'port_speed': port_speeds, + 'guest_disk': local_disk, + 'flavors': self.guest.getCreateObjectOptions()['flavors'] + } + + @retry(logger=LOGGER) + def _get_package(self, package_keyname): + """Get the package related to simple hardware ordering.""" + mask = ''' + items[ + description, keyName, capacity, + attributes[id,attributeTypeKeyName], + itemCategory[ id, categoryCode], + softwareDescription[id,referenceCode,longDescription],prices], + regions[location[location[priceGroups]]] + ''' + package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + return package def cancel_instance(self, instance_id): """Cancel an instance immediately, deleting all its data. diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index c9b6c9c0b..fd562f69d 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -314,14 +314,10 @@ def test_detail_vs_ptr_error(self): self.assertEqual(output.get('ptr', None), None) def test_create_options(self): - result = self.run_command(['vs', 'create-options']) + result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT']) self.assert_no_fail(result) - self.assertIn('datacenter', result.output) - self.assertIn('flavor', result.output) - self.assertIn('memory', result.output) - self.assertIn('cpu', result.output) - self.assertIn('OS', result.output) - self.assertIn('network', result.output) + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): @@ -887,6 +883,6 @@ def test_credentail(self): result = self.run_command(['vs', 'credentials', '100']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), [{ - "username": "user", - "password": "pass" + "username": "user", + "password": "pass" }]) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index a9f256c80..c00941433 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,10 +116,9 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - results = self.vs.get_create_options() - - self.assertEqual( - fixtures.SoftLayer_Virtual_Guest.getCreateObjectOptions, results) + self.vs.get_create_options('PUBLIC_CLOUD_SERVER') + self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') def test_cancel_instance(self): result = self.vs.cancel_instance(1) From be524bb9d29cd9670d3492baed83ced94895982b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 1 Sep 2020 16:44:37 -0500 Subject: [PATCH 0456/1582] #1299 did a lot of work getting location specific pricing working. Still needs testing --- SoftLayer/CLI/hardware/create_options.py | 12 +- SoftLayer/managers/hardware.py | 136 +++++++++++++++++------ 2 files changed, 106 insertions(+), 42 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 1ae06a9be..abc36d845 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -7,18 +7,17 @@ from SoftLayer.CLI import formatting from SoftLayer.managers import hardware - @click.command() @click.argument('location', required=False) -@click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and ' - 'to list the Item Prices by location, add it to the ' - '--prices option using location KeyName, e.g. --prices AMSTERDAM02') +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) - options = hardware_manager.get_create_options() + options = hardware_manager.get_create_options(location) tables = [] @@ -35,9 +34,6 @@ def cli(env, prices, location=None): _os_prices_table(options['operating_systems'], tables) _port_speed_prices_table(options['port_speeds'], tables) _extras_prices_table(options['extras'], tables) - if location: - location_prices = hardware_manager.get_hardware_item_prices(location) - _location_item_prices(location_prices, tables) else: # Presets preset_table = formatting.Table(['Size', 'Value'], title="Sizes") diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 660838870..a10586b65 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -379,18 +379,38 @@ def get_cancellation_reasons(self): } @retry(logger=LOGGER) - def get_create_options(self): - """Returns valid options for ordering hardware.""" + def get_create_options(self, datacenter=None): + """Returns valid options for ordering hardware. + :param string datacenter: short name, like dal09 + """ + package = self._get_package() + location_group_id = None + if datacenter: + _filter = {"name":{"operation":datacenter}} + _mask = "mask[priceGroups]" + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', + mask=_mask, filter=_filter, limit=1) + if not dc_details: + raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) + # A DC will have several price groups, no good way to deal with this other than checking each. + # An item should only belong to one type of price group. + for group in dc_details[0].get('priceGroups', []): + # We only care about SOME of the priceGroups, which all SHOULD start with `Location Group X` + # Hopefully this never changes.... + if group.get('description').startswith('Location'): + location_group_id = group.get('id') + # Locations locations = [] for region in package['regions']: - locations.append({ - 'name': region['location']['location']['longName'], - 'key': region['location']['location']['name'], - }) + if datacenter is None or datacenter == region['location']['location']['name']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) # Sizes sizes = [] @@ -398,8 +418,8 @@ def get_create_options(self): sizes.append({ 'name': preset['description'], 'key': preset['keyName'], - 'hourlyRecurringFee': _get_preset_cost(preset['prices'], 'hourly'), - 'recurringFee': _get_preset_cost(preset['prices'], 'monthly') + 'hourlyRecurringFee': _get_preset_cost(preset, package['items'], 'hourly', location_group_id), + 'recurringFee': _get_preset_cost(preset, package['items'], 'monthly', location_group_id) }) operating_systems = [] @@ -413,7 +433,7 @@ def get_create_options(self): 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], 'referenceCode': item['softwareDescription']['referenceCode'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) # Port speeds elif category == 'port_speed': @@ -421,14 +441,14 @@ def get_create_options(self): 'name': item['description'], 'speed': item['capacity'], 'key': item['keyName'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) # Extras elif category in EXTRA_CATEGORIES: extras.append({ 'name': item['description'], 'key': item['keyName'], - 'prices': get_item_price(item['prices']) + 'prices': get_item_price(item['prices'], location_group_id) }) return { @@ -442,21 +462,25 @@ def get_create_options(self): @retry(logger=LOGGER) def _get_package(self): """Get the package related to simple hardware ordering.""" - mask = ''' - items[ - keyName, - capacity, - description, - attributes[id,attributeTypeKeyName], - itemCategory[id,categoryCode], - softwareDescription[id,referenceCode,longDescription], - prices - ], - activePresets[prices], - accountRestrictedActivePresets[prices], - regions[location[location[priceGroups]]] - ''' - package = self.ordering_manager.get_package_by_key(self.package_keyname, mask=mask) + from pprint import pprint as pp + items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ + 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ + 'prices]' + # The preset prices list will only have default prices. The prices->item->prices will have location specific + presets_mask = 'mask[prices]' + region_mask = 'location[location[priceGroups]]' + package = {'items': None,'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") + + package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', + id=package_info.get('id'), mask=items_mask) + package['activePresets'] = self.client.call('SoftLayer_Product_Package', 'getActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['accountRestrictedActivePresets'] = self.client.call('SoftLayer_Product_Package', + 'getAccountRestrictedActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['regions'] = self.client.call('SoftLayer_Product_Package', 'getRegions', + id=package_info.get('id'), mask=region_mask) return package def _generate_create_dict(self, @@ -856,21 +880,65 @@ def _get_location(package, location): raise SoftLayerError("Could not find valid location for: '%s'" % location) -def _get_preset_cost(prices, type_cost): - """Get the preset cost.""" +def _get_preset_cost(preset, items, type_cost, location_group_id=None): + """Get the preset cost. + + :param preset list: SoftLayer_Product_Package_Preset[] + :param items list: SoftLayer_Product_Item[] + :param type_cost string: 'hourly' or 'monthly' + :param location_group_id int: locationGroupId's to get price for. + """ + + # Location based pricing on presets is a huge pain. Requires a few steps + # 1. Get the presets prices, which are only ever the default prices + # 2. Get that prices item ID, and use that to match the packages item + # 3. find the package item, THEN find that items prices + # 4. from those item prices, find the one that matches your locationGroupId + item_cost = 0.00 - for price in prices: - if type_cost == 'hourly': - item_cost += float(price['hourlyRecurringFee']) + if type_cost == 'hourly': + cost_key = 'hourlyRecurringFee' + else: + cost_key = 'recurringFee' + for price in preset.get('prices', []): + # Need to find the location specific price + if location_group_id: + # Find the item in the packages item list + for item in items: + # Same item as the price's item + if item.get('id') == price.get('itemId'): + # Find the items location specific price. + for location_price in item.get('prices', []): + if location_price.get('locationGroupId', 0) == location_group_id: + item_cost += float(location_price.get(cost_key)) else: - item_cost += float(price['recurringFee']) + item_cost += float(price.get(cost_key)) return item_cost -def get_item_price(prices): - """Get item prices""" +def get_item_price(prices, location_group_id=None): + """Get item prices, optionally for a specific location. + + Will return the default pricing information if there isn't any location specific pricing. + + :param prices list: SoftLayer_Product_Item_Price[] + :param location_group_id int: locationGroupId's to get price for. + """ prices_list = [] + location_price = [] for price in prices: + # Only look up location prices if we need to + if location_group_id: + if price['locationGroupId'] == location_group_id: + location_price.append(price) + # Always keep track of default prices if not price['locationGroupId']: prices_list.append(price) + + # If this item has location specific pricing, return that + if location_price: + return location_price + + # Otherwise reutrn the default price list. return prices_list + \ No newline at end of file From 3a513c0db9f69e04d8577c0244493d486542faca Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:00:42 -0400 Subject: [PATCH 0457/1582] Refactor hw create-options. --- .../fixtures/SoftLayer_Product_Package.py | 87 ++++++++++++++++++- SoftLayer/managers/hardware.py | 8 +- tests/CLI/modules/server_tests.py | 21 +++-- tests/managers/hardware_tests.py | 26 +++--- 4 files changed, 110 insertions(+), 32 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 9a2019909..91839447e 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -859,6 +859,11 @@ 'capacity': '1000', 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'softwareDescription': { + 'id': 1228, + 'longDescription': 'Redhat EL 5.10-64', + 'referenceCode': 'REDHAT_5_64' + }, 'prices': [{'id': 1122, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0, @@ -994,11 +999,11 @@ }, { 'id': 66464, - 'keyName': 'KeyName0211', + 'keyName': '1_IPV6_ADDRESS', 'capacity': '64', 'description': '/64 Block Portable Public IPv6 Addresses', 'itemCategory': {'categoryCode': 'static_ipv6_addresses'}, - 'prices': [{'id': 664641, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 664641, 'hourlyRecurringFee': '0', 'locationGroupId': '', 'recurringFee': '0'}], }, { 'id': 610, @@ -1007,7 +1012,64 @@ 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], - }] + }, + {'attributes': [], + 'capacity': '0', + 'description': '0 GB Bandwidth', + 'itemCategory': {'categoryCode': 'bandwidth', 'id': 10}, + 'keyName': 'BANDWIDTH_0_GB_2', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 1800, + "locationGroupId": '', + 'itemId': 439, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'setupFee': '0', + 'sort': 99}]}, + {'attributes': [], + 'capacity': '10', + 'description': '10 Mbps Public & Private Network Uplinks', + 'itemCategory': {'categoryCode': 'port_speed', 'id': 26}, + 'keyName': '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 272, + "locationGroupId": '', + 'itemId': 186, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 5}]}, + {'attributes': [], + 'capacity': '0', + 'description': 'Ubuntu Linux 14.04 LTS Trusty Tahr (64 bit)', + 'itemCategory': {'categoryCode': 'os', 'id': 12}, + 'keyName': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'prices': [{'accountRestrictions': [], + 'currentPriceFlag': '', + 'hourlyRecurringFee': '0', + 'id': 37650, + "locationGroupId": '', + 'itemId': 4702, + 'laborFee': '0', + 'onSaleFlag': '', + 'oneTimeFee': '0', + 'quantity': '', + 'recurringFee': '0', + 'setupFee': '0', + 'sort': 9}], + 'softwareDescription': {'id': 1362, + 'longDescription': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64'}} +] getItemPricesISCSI = [ { @@ -1504,6 +1566,25 @@ getRegions = [{ "description": "WDC07 - Washington, DC", "keyname": "WASHINGTON07", + "location": { + "locationId": 2017603, + "location": { + "id": 2017603, + "longName": "Washington 7", + "name": "wdc07", + "priceGroups": [ + { + "description": "COS Regional - US East", + "id": 1305, + "locationGroupTypeId": 82, + "name": "us-east", + "locationGroupType": { + "name": "PRICING" + } + } + ] + } + }, "locations": [{ "location": { "euCompliantFlag": False, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index a10586b65..54efef5d2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -389,7 +389,7 @@ def get_create_options(self, datacenter=None): location_group_id = None if datacenter: - _filter = {"name":{"operation":datacenter}} + _filter = {"name": {"operation": datacenter}} _mask = "mask[priceGroups]" dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) @@ -462,14 +462,13 @@ def get_create_options(self, datacenter=None): @retry(logger=LOGGER) def _get_package(self): """Get the package related to simple hardware ordering.""" - from pprint import pprint as pp items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ - 'prices]' + 'prices[categories]]' # The preset prices list will only have default prices. The prices->item->prices will have location specific presets_mask = 'mask[prices]' region_mask = 'location[location[priceGroups]]' - package = {'items': None,'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package = {'items': None, 'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', @@ -941,4 +940,3 @@ def get_item_price(prices, location_group_id=None): # Otherwise reutrn the default price list. return prices_list - \ No newline at end of file diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index ebf3dc1c0..48a0fc100 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -360,28 +360,27 @@ def test_create_options(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[0][0]['Value'], 'wdc01') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assertEqual(output[0][0]['Value'], 'wdc07') def test_create_options_prices(self): result = self.run_command(['server', 'create-options', '--prices']) + print("-----------------") + print(result.output) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') - self.assertEqual(output[3][0]['Monthly'], '0') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') + self.assertEqual(output[1][0]['Size'], 'M1.64x512x25') def test_create_options_location(self): - result = self.run_command(['server', 'create-options', '--prices', 'AMSTERDAM02']) + result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], "%.4f" % 780.0) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 1.18) - self.assertEqual(output[1][0]['Value'], 'S1270_8GB_2X1TBSATA_NORAID') - self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + self.assertEqual(output[1][0]['Monthly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) + self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index b6cfc7724..8b361b935 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -120,7 +120,7 @@ def test_get_create_options(self): options = self.hardware.get_create_options() extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} - locations = {'key': 'wdc01', 'name': 'Washington 1'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64', @@ -132,10 +132,10 @@ def test_get_create_options(self): 'name': '10 Mbps Public & Private Network Uplinks' } sizes = { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'hourlyRecurringFee': 1.18, - 'recurringFee': 780.0 + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 } self.assertEqual(options['extras'][0]['key'], extras['key']) @@ -158,7 +158,7 @@ def test_get_create_options_prices(self): } ] } - locations = {'key': 'wdc01', 'name': 'Washington 1'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} operating_systems = { 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'name': 'Ubuntu / 14.04-64', @@ -186,10 +186,10 @@ def test_get_create_options_prices(self): ] } sizes = { - 'key': 'S1270_8GB_2X1TBSATA_NORAID', - 'name': 'Single Xeon 1270, 8GB Ram, 2x1TB SATA disks, Non-RAID', - 'hourlyRecurringFee': 1.18, - 'recurringFee': 780.0 + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 } self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], @@ -258,7 +258,7 @@ def test_generate_create_dict(self): 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', 'domain': 'giggles.woo', - 'location': 'wdc01', + 'location': 'wdc07', 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'port_speed': 10, 'hourly': True, @@ -268,7 +268,7 @@ def test_generate_create_dict(self): } package = 'BARE_METAL_SERVER' - location = 'wdc01' + location = 'wdc07' item_keynames = [ '1_IP_ADDRESS', 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', @@ -304,7 +304,7 @@ def test_generate_create_dict_network_key(self): 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'test1', 'domain': 'test.com', - 'location': 'wdc01', + 'location': 'wdc07', 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'network': 'NETWORKING', 'hourly': True, From faa829bfcfaae440498acb85eafb96853a52675a Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 3 Sep 2020 16:27:33 -0400 Subject: [PATCH 0458/1582] #1336 add Invoice Item id as param valid in account item-detail command --- SoftLayer/CLI/account/item_detail.py | 104 +++++++++--------- .../SoftLayer_Billing_Invoice_Item.py | 3 + SoftLayer/managers/account.py | 25 ++++- tests/managers/account_tests.py | 16 +++ 4 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index 7a2c53df3..9af5b4bf2 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -1,52 +1,52 @@ -"""Gets some details about a specific billing item.""" -# :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() -@click.argument('identifier') -@environment.pass_env -def cli(env, identifier): - """Gets detailed information about a billing item.""" - manager = AccountManager(env.client) - item = manager.get_billing_item(identifier) - env.fout(item_table(item)) - - -def item_table(item): - """Formats a table for billing items""" - - date_format = '%Y-%m-%d' - table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) - table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) - table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) - table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) - table.add_row(['description', item.get('description')]) - fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) - if fqdn != ".": - table.add_row(['FQDN', fqdn]) - - if item.get('hourlyFlag', False): - table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) - table.add_row(['hoursUsed', item.get('hoursUsed')]) - table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) - else: - table.add_row(['recurringFee', item.get('recurringFee')]) - - ordered_by = "IBM" - user = utils.lookup(item, 'orderItem', 'order', 'userRecord') - if user: - ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) - table.add_row(['Ordered By', ordered_by]) - table.add_row(['Notes', item.get('notes')]) - table.add_row(['Location', utils.lookup(item, 'location', 'name')]) - if item.get('children'): - for child in item.get('children'): - table.add_row([child.get('categoryCode'), child.get('description')]) - - return table +"""Gets some details about a specific billing item.""" +# :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() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Gets detailed information about a billing item.""" + manager = AccountManager(env.client) + item = manager.get_item_detail(identifier) + env.fout(item_table(item)) + + +def item_table(item): + """Formats a table for billing items""" + + date_format = '%Y-%m-%d' + table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) + table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) + table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) + table.add_row(['description', item.get('description')]) + fqdn = "{}.{}".format(item.get('hostName'), item.get('domain')) + if fqdn != ".": + table.add_row(['FQDN', fqdn]) + + if item.get('hourlyFlag', False): + table.add_row(['hourlyRecurringFee', item.get('hourlyRecurringFee')]) + table.add_row(['hoursUsed', item.get('hoursUsed')]) + table.add_row(['currentHourlyCharge', item.get('currentHourlyCharge')]) + else: + table.add_row(['recurringFee', item.get('recurringFee')]) + + ordered_by = "IBM" + user = utils.lookup(item, 'orderItem', 'order', 'userRecord') + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + table.add_row(['Notes', item.get('notes')]) + table.add_row(['Location', utils.lookup(item, 'location', 'name')]) + if item.get('children'): + for child in item.get('children'): + table.add_row([child.get('categoryCode'), child.get('description')]) + + return table diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py b/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py new file mode 100644 index 000000000..4be040cbe --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Invoice_Item.py @@ -0,0 +1,3 @@ +from SoftLayer.fixtures.SoftLayer_Billing_Item import getObject as billingItem + +getBillingItem = billingItem diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 56f951c28..ff595f8aa 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -8,6 +8,7 @@ import logging +from SoftLayer import SoftLayerAPIError from SoftLayer import utils # Invalid names are ignored due to long method names and short argument names @@ -205,7 +206,7 @@ def get_account_billing_items(self, mask=None): def get_billing_item(self, identifier, mask=None): """Gets details about a billing item - :param int identifier Billing_Item id + :param int identifier: Billing_Item id :param string mask: Object mask to use. :return: Billing_Item """ @@ -219,6 +220,28 @@ def get_billing_item(self, identifier, mask=None): return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) + def get_billing_item_from_invoice(self, identifier): + """Gets details about a billing item of a billing invoice item + + :param int identifier: Billing_Invoice_Item id + :return: Billing_Item + """ + return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier) + + def get_item_detail(self, identifier): + """Gets details about a billing item + + :param int identifier: Billing_Item id or Billing_Invoice_Item + :return: Billing_Item + """ + + try: + return self.get_billing_item(identifier) + except SoftLayerAPIError as exception: + if exception.faultCode == 404: + return self.get_billing_item_from_invoice(identifier) + raise + def cancel_item(self, identifier, reason="No longer needed", note=None): """Cancels a specific billing item with a reason diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index b47ec6abb..b051e5ee7 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -5,6 +5,7 @@ """ from SoftLayer.managers.account import AccountManager as AccountManager +from SoftLayer import SoftLayerAPIError from SoftLayer import testing @@ -133,3 +134,18 @@ def test_cancel_item(self): self.manager.cancel_item(12345, reason, note) self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', args=(False, True, reason, note), identifier=12345) + + def test_get_billing_item_from_invoice(self): + self.manager.get_billing_item_from_invoice(12345) + self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=12345) + + def test_get_item_details_with_billing_item_id(self): + self.manager.get_item_detail(12345) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=12345) + + def test_get_item_details_with_invoice_item_id(self): + mock = self.set_mock('SoftLayer_Billing_Item', 'getObject') + mock.side_effect = SoftLayerAPIError(404, "Unable to find object with id of '123456'.") + self.manager.get_item_detail(123456) + self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=123456) + self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=123456) From 6b8bbbb4e5a40076aecd6a2ddad3807514db4e5b Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:39:24 -0400 Subject: [PATCH 0459/1582] Fix tox test. --- .../fixtures/SoftLayer_Product_Package.py | 47 +++++++++++++++++++ tests/CLI/modules/vs/vs_create_tests.py | 3 ++ 2 files changed, 50 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 91839447e..f47bca334 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1138,6 +1138,53 @@ 'sort': 0 }] +getItemsVS = [ + { + 'id': 1234, + 'keyName': 'KeyName01', + 'capacity': '1000', + 'description': 'Public & Private Networks', + 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'softwareDescription': { + 'id': 1228, + 'longDescription': 'Redhat EL 5.10-64', + 'referenceCode': 'REDHAT_5_64' + }, + 'prices': [{'id': 1122, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 26, + 'name': 'Uplink Port Speeds', + 'categoryCode': 'port_speed'}]}], + }, + { + 'id': 2233, + 'keyName': 'KeyName02', + 'capacity': '1000', + 'description': 'Public & Private Networks', + 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, + 'prices': [{'id': 4477, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 26, + 'name': 'Uplink Port Speeds', + 'categoryCode': 'port_speed'}]}], + }, + { + 'id': 1239, + 'keyName': 'KeyName03', + 'capacity': '2', + 'description': 'RAM', + 'itemCategory': {'categoryCode': 'RAM'}, + 'prices': [{'id': 1133, + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0, + 'categories': [{'id': 3, + 'name': 'RAM', + 'categoryCode': 'ram'}]}], + } +] + verifyOrderDH = { 'preTaxSetup': '0', 'storageGroups': [], diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 761778db6..413bb6c18 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -639,6 +639,9 @@ def test_create_with_ipv6_no_prices(self, confirm_mock): Since its hard to test if the price ids gets added to placeOrder call, this test juse makes sure that code block isn't being skipped """ + confirm_mock.return_value = True + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = SoftLayer_Product_Package.getItemsVS result = self.run_command(['vs', 'create', '--test', '--hostname', 'TEST', '--domain', 'TESTING', '--flavor', 'B1_2X8X25', '--datacenter', 'TEST00', '--os', 'UBUNTU_LATEST', From b7b3dd184b0656a7432ca482bce5fd0e08cfaf19 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 3 Sep 2020 16:49:00 -0400 Subject: [PATCH 0460/1582] Fix tox analysis. --- SoftLayer/CLI/hardware/create_options.py | 1 + SoftLayer/managers/hardware.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index abc36d845..d61695be3 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 hardware + @click.command() @click.argument('location', required=False) @click.option('--prices', '-p', is_flag=True, diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 54efef5d2..849026ed4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -384,15 +384,14 @@ def get_create_options(self, datacenter=None): :param string datacenter: short name, like dal09 """ - + package = self._get_package() location_group_id = None if datacenter: _filter = {"name": {"operation": datacenter}} _mask = "mask[priceGroups]" - dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', - mask=_mask, filter=_filter, limit=1) + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) if not dc_details: raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) # A DC will have several price groups, no good way to deal with this other than checking each. @@ -881,7 +880,7 @@ def _get_location(package, location): def _get_preset_cost(preset, items, type_cost, location_group_id=None): """Get the preset cost. - + :param preset list: SoftLayer_Product_Package_Preset[] :param items list: SoftLayer_Product_Item[] :param type_cost string: 'hourly' or 'monthly' From d1ff3fb9e657329898af71c3be5c6a7599a71bf4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 7 Sep 2020 14:16:53 -0400 Subject: [PATCH 0461/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 32 ++++++++++++----- tests/CLI/modules/server_tests.py | 2 -- tests/managers/hardware_tests.py | 58 +++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 11 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 849026ed4..53939f2e1 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -410,7 +410,6 @@ def get_create_options(self, datacenter=None): 'name': region['location']['location']['longName'], 'key': region['location']['location']['name'], }) - # Sizes sizes = [] for preset in package['activePresets'] + package['accountRestrictedActivePresets']: @@ -467,7 +466,7 @@ def _get_package(self): # The preset prices list will only have default prices. The prices->item->prices will have location specific presets_mask = 'mask[prices]' region_mask = 'location[location[priceGroups]]' - package = {'items': None, 'activePresets': None, 'accountRestrictedActivePresets': None, 'regions': None} + package = {'items': [], 'activePresets': [], 'accountRestrictedActivePresets': [], 'regions': []} package_info = self.ordering_manager.get_package_by_key(self.package_keyname, mask="mask[id]") package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', @@ -902,18 +901,33 @@ def _get_preset_cost(preset, items, type_cost, location_group_id=None): # Need to find the location specific price if location_group_id: # Find the item in the packages item list - for item in items: - # Same item as the price's item - if item.get('id') == price.get('itemId'): - # Find the items location specific price. - for location_price in item.get('prices', []): - if location_price.get('locationGroupId', 0) == location_group_id: - item_cost += float(location_price.get(cost_key)) + item_cost = find_item_in_package(cost_key, items, location_group_id, price) else: item_cost += float(price.get(cost_key)) return item_cost +def find_item_in_package(cost_key, items, location_group_id, price): + """Find the item in the packages item list. + + Will return the item cost. + + :param string cost_key: item cost key hourlyRecurringFee or recurringFee. + :param list items: items list. + :param int location_group_id: locationGroupId's to get price for. + :param price: price data. + """ + item_cost = 0.00 + for item in items: + # Same item as the price's item + if item.get('id') == price.get('itemId'): + # Find the items location specific price. + for location_price in item.get('prices', []): + if location_price.get('locationGroupId', 0) == location_group_id: + item_cost += float(location_price.get(cost_key)) + return item_cost + + def get_item_price(prices, location_group_id=None): """Get item prices, optionally for a specific location. diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 48a0fc100..dc9d70726 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -365,8 +365,6 @@ def test_create_options(self): def test_create_options_prices(self): result = self.run_command(['server', 'create-options', '--prices']) - print("-----------------") - print(result.output) self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 8b361b935..e5b532647 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -200,6 +200,64 @@ def test_get_create_options_prices(self): self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) self.assertEqual(options['sizes'][0], sizes) + def test_get_create_options_prices_by_location(self): + options = self.hardware.get_create_options('wdc07') + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + print("---------") + print(options) + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speeds'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) + def test_get_hardware_item_prices(self): options = self.hardware.get_hardware_item_prices("MONTREAL") item_prices = [ From a96c034f1609d2d4612c64b0c27d1ec62fd809b3 Mon Sep 17 00:00:00 2001 From: try Date: Thu, 10 Sep 2020 22:12:09 +0530 Subject: [PATCH 0462/1582] final repo-modified the help console comments --- SoftLayer/CLI/block/refresh.py | 2 +- SoftLayer/CLI/file/refresh.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 0c94ae67f..9e610f04a 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,7 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) diff --git a/SoftLayer/CLI/file/refresh.py b/SoftLayer/CLI/file/refresh.py index 765e82730..8e2b1c543 100644 --- a/SoftLayer/CLI/file/refresh.py +++ b/SoftLayer/CLI/file/refresh.py @@ -11,7 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """"Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent.""" file_manager = SoftLayer.FileStorageManager(env.client) resp = file_manager.refresh_dupe(volume_id, snapshot_id) From 7669134285cee0c55cb3809cd8bb3f2e6f1aaea5 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 10 Sep 2020 19:16:32 -0400 Subject: [PATCH 0463/1582] fix the Christopher code review --- SoftLayer/CLI/virt/create_options.py | 22 ++++++++++----------- SoftLayer/managers/vs.py | 29 ++++++++++++++++++---------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 64a2c71a3..4e73d55be 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -9,15 +9,14 @@ @click.command(short_help="Get options to use for creating virtual servers.") -@click.option('--vsi-type', required=False, type=click.Choice(['TRANSIENT', 'SUSPEND']), - help="Billing rate") +@click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', + type=click.Choice(['TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'PUBLIC_CLOUD_SERVER']), + help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " + "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, PUBLIC_CLOUD_SERVER") @environment.pass_env def cli(env, vsi_type): """Virtual server order options.""" - if vsi_type is None: - vsi_type = 'PUBLIC' - vsi_type = vsi_type + '_CLOUD_SERVER' vsi = SoftLayer.VSManager(env.client) options = vsi.get_create_options(vsi_type) @@ -41,13 +40,14 @@ def cli(env, vsi_type): os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) tables.append(os_table) - flavors_table = formatting.Table(['flavor', 'Name'], title="Flavors") - flavors_table.sortby = 'Name' - flavors_table.align = 'l' + # Sizes + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' - for flavor in options['flavors']: - flavors_table.add_row([flavor['flavor']['keyName'], flavor['flavor']['name']]) - tables.append(flavors_table) + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) # RAM ram_table = formatting.Table(['memory', 'Value'], title="RAM") diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6c6fec747..1ddbdc3e1 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -249,7 +249,7 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self, vsi_type): + def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): """Retrieves the available options for creating a VS. :returns: A dictionary of creation options. @@ -279,6 +279,14 @@ def get_create_options(self, vsi_type): guest_core = [] local_disk = [] ram = [] + + sizes = [] + for preset in package['activePresets'] + package['accountRestrictedActivePresets']: + sizes.append({ + 'name': preset['description'], + 'key': preset['keyName'] + }) + for item in package['items']: category = item['itemCategory']['categoryCode'] # Operating systems @@ -332,21 +340,22 @@ def get_create_options(self, vsi_type): 'guest_core': guest_core, 'port_speed': port_speeds, 'guest_disk': local_disk, - 'flavors': self.guest.getCreateObjectOptions()['flavors'] + 'sizes': sizes } @retry(logger=LOGGER) def _get_package(self, package_keyname): """Get the package related to simple hardware ordering.""" mask = ''' - items[ - description, keyName, capacity, - attributes[id,attributeTypeKeyName], - itemCategory[ id, categoryCode], - softwareDescription[id,referenceCode,longDescription],prices], - regions[location[location[priceGroups]]] - ''' - package = self.ordering_manager.get_package_by_key(package_keyname, mask=mask) + activePresets, accountRestrictedActivePresets, + items[description, keyName, capacity, + attributes[id, attributeTypeKeyName], + itemCategory[id, categoryCode], + softwareDescription[id, referenceCode, longDescription], prices], + regions[location[location[priceGroups]]]''' + + package_id = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]")['id'] + package = self.client.call('Product_Package', 'getObject', id=package_id, mask=mask) return package def cancel_instance(self, instance_id): From 77248dc2569ea3c890a27d605bef24df9ef89e7c Mon Sep 17 00:00:00 2001 From: try Date: Fri, 11 Sep 2020 17:57:28 +0530 Subject: [PATCH 0464/1582] removed extra line --- SoftLayer/CLI/block/refresh.py | 1 - SoftLayer/managers/storage.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 9e610f04a..369a6c815 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -16,4 +16,3 @@ def cli(env, volume_id, snapshot_id): resp = block_manager.refresh_dupe(volume_id, snapshot_id) click.echo(resp) - diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index cd582701d..8a3c816d2 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -423,8 +423,6 @@ def refresh_dupe(self, volume_id, snapshot_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. From d9185a721dd0926f2ba37fcc0b5404403dd825b8 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 11 Sep 2020 21:02:46 -0400 Subject: [PATCH 0465/1582] add to get_billing_item_from_invoice order user information --- SoftLayer/managers/account.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index ff595f8aa..884e4335b 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -22,6 +22,11 @@ class AccountManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + _DEFAULT_BILLING_ITEM_MASK = """mask[ + orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], + nextInvoiceTotalRecurringAmount, + location, hourlyFlag, children + ]""" def __init__(self, client): self.client = client @@ -212,21 +217,20 @@ def get_billing_item(self, identifier, mask=None): """ if mask is None: - mask = """mask[ - orderItem[id,order[id,userRecord[id,email,displayName,userStatus]]], - nextInvoiceTotalRecurringAmount, - location, hourlyFlag, children - ]""" + mask = self._DEFAULT_BILLING_ITEM_MASK return self.client.call('Billing_Item', 'getObject', id=identifier, mask=mask) - def get_billing_item_from_invoice(self, identifier): + def get_billing_item_from_invoice(self, identifier, mask=None): """Gets details about a billing item of a billing invoice item :param int identifier: Billing_Invoice_Item id + :param mask: Object mask to use. :return: Billing_Item """ - return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier) + if mask is None: + mask = self._DEFAULT_BILLING_ITEM_MASK + return self.client.call('Billing_Invoice_Item', 'getBillingItem', id=identifier, mask=mask) def get_item_detail(self, identifier): """Gets details about a billing item From f4af694d7a784e68e6bdd0acc6654d77202f4c8b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Sep 2020 15:11:07 -0400 Subject: [PATCH 0466/1582] fix tox tool and fix unit test --- .../fixtures/SoftLayer_Product_Package.py | 105 ++++++++++++++++++ techbabble.xyz.crt | 2 + techbabble.xyz.csr | 3 + techbabble.xyz.icc | 0 techbabble.xyz.key | 3 + tests/CLI/modules/vs/vs_tests.py | 4 +- tests/managers/vs/vs_tests.py | 6 +- 7 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 techbabble.xyz.crt create mode 100644 techbabble.xyz.csr create mode 100644 techbabble.xyz.icc create mode 100644 techbabble.xyz.key diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 07aefab69..2549847d4 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1779,3 +1779,108 @@ ] } ] + +getObject = { + 'id': 200, + 'regions': [{'description': 'WDC01 - Washington, DC - East Coast U.S.', + 'keyname': 'WASHINGTON_DC', + 'location': {'location': {'id': 37473, + 'longName': 'Washington 1', + 'name': 'wdc01'}}, + 'sortOrder': 10}], + 'accountRestrictedActivePresets': [], + 'activePresets': [ + { + 'description': 'AC2.8x60x25', + 'id': 861, + 'isActive': '1', + 'keyName': 'AC2_8X60X25', + 'name': 'AC2.8x60x25', + 'packageId': 835 + }, + { + 'description': 'AC2.8x60x100', + 'id': 863, + 'isActive': '1', + 'keyName': 'AC2_8X60X100', + 'name': 'AC2.8x60x100', + 'packageId': 835 + }], + "items": [{ + "capacity": "56", + "description": "56 Cores x 360 RAM x 1.2 TB x 2 GPU P100 [encryption enabled]", + "bundleItems": [ + { + "capacity": "1200", + "keyName": "1.2 TB Local Storage (Dedicated Host Capacity)", + "categories": [{ + "categoryCode": "dedicated_host_disk" + }] + }, + { + "capacity": "242", + "keyName": "2_GPU_P100_DEDICATED", + "hardwareGenericComponentModel": { + "capacity": "16", + "id": 849, + "hardwareComponentType": { + "id": 20, + "keyName": "GPU" + } + }, + "categories": [{ + "categoryCode": "dedicated_host_ram" + }, { + "capacity": "2", + "description": "2 x 2.0 GHz or higher Cores", + "keyName": "GUEST_CORES_2", + "attributes": [ + { + "id": 8261, + "attributeTypeKeyName": "ORDER_SAVES_USAGE_FEES" + } + ], + "itemCategory": { + "categoryCode": "guest_core", + "id": 80 + }}] + } + ], + "prices": [ + { + "itemId": 10195, + "setupFee": "0", + "recurringFee": "2099", + "tierMinimumThreshold": "", + "hourlyRecurringFee": "3.164", + "oneTimeFee": "0", + "currentPriceFlag": "", + "id": 200269, + "sort": 0, + "onSaleFlag": "", + "laborFee": "0", + "locationGroupId": "", + "quantity": "" + }, + { + "itemId": 10195, + "setupFee": "0", + "recurringFee": "2161.97", + "tierMinimumThreshold": "", + "hourlyRecurringFee": "3.258", + "oneTimeFee": "0", + "currentPriceFlag": "", + "id": 200271, + "sort": 0, + "onSaleFlag": "", + "laborFee": "0", + "locationGroupId": 503, + "quantity": "" + } + ], + "keyName": "56_CORES_X_484_RAM_X_1_5_TB_X_2_GPU_P100", + "id": 10195, + "itemCategory": { + "categoryCode": "dedicated_virtual_hosts" + } + }]} diff --git a/techbabble.xyz.crt b/techbabble.xyz.crt new file mode 100644 index 000000000..01d72d87f --- /dev/null +++ b/techbabble.xyz.crt @@ -0,0 +1,2 @@ +-----BEGIN CERTIFICATE----- +MIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT -----END CERTIFICATE----- \ No newline at end of file diff --git a/techbabble.xyz.csr b/techbabble.xyz.csr new file mode 100644 index 000000000..78d978dca --- /dev/null +++ b/techbabble.xyz.csr @@ -0,0 +1,3 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G +-----END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/techbabble.xyz.icc b/techbabble.xyz.icc new file mode 100644 index 000000000..e69de29bb diff --git a/techbabble.xyz.key b/techbabble.xyz.key new file mode 100644 index 000000000..1d906abc7 --- /dev/null +++ b/techbabble.xyz.key @@ -0,0 +1,3 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index fd562f69d..a680e3c72 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -314,10 +314,8 @@ def test_detail_vs_ptr_error(self): self.assertEqual(output.get('ptr', None), None) def test_create_options(self): - result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT']) + result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index c00941433..cf701d21f 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,9 +116,9 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - self.vs.get_create_options('PUBLIC_CLOUD_SERVER') - self.assert_called_with('SoftLayer_Product_Package', 'getAllObjects') - self.assert_called_with('SoftLayer_Virtual_Guest', 'getCreateObjectOptions') + self.vs.get_create_options() + self.assert_called_with('SoftLayer_Product_Package', 'getObject', + identifier=200) def test_cancel_instance(self): result = self.vs.cancel_instance(1) From 9599d22bbd8041f3004cc9b0a1625b715efb5742 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Sep 2020 15:12:08 -0400 Subject: [PATCH 0467/1582] fix tox tool and fix unit test --- techbabble.xyz.crt | 2 -- techbabble.xyz.csr | 3 --- techbabble.xyz.icc | 0 techbabble.xyz.key | 3 --- 4 files changed, 8 deletions(-) delete mode 100644 techbabble.xyz.crt delete mode 100644 techbabble.xyz.csr delete mode 100644 techbabble.xyz.icc delete mode 100644 techbabble.xyz.key diff --git a/techbabble.xyz.crt b/techbabble.xyz.crt deleted file mode 100644 index 01d72d87f..000000000 --- a/techbabble.xyz.crt +++ /dev/null @@ -1,2 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEJTCCAw2gAwIBAgIDCbQ0MA0GCSqGSIb3DQEBCwUAMEcxCzAJBgNVBAYTAlVT -----END CERTIFICATE----- \ No newline at end of file diff --git a/techbabble.xyz.csr b/techbabble.xyz.csr deleted file mode 100644 index 78d978dca..000000000 --- a/techbabble.xyz.csr +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN CERTIFICATE REQUEST----- -MIIC1jCCAb4CAQAwgZAxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh123456QMA4G ------END CERTIFICATE REQUEST----- \ No newline at end of file diff --git a/techbabble.xyz.icc b/techbabble.xyz.icc deleted file mode 100644 index e69de29bb..000000000 diff --git a/techbabble.xyz.key b/techbabble.xyz.key deleted file mode 100644 index 1d906abc7..000000000 --- a/techbabble.xyz.key +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA3SwTZ7sh7we5zIbmtSbxGJxff07eutrK12345678WXtwQSdE ------END RSA PRIVATE KEY----- \ No newline at end of file From b69d6c7ba229bf1421de8ce26caac7fd8d952eec Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 15 Sep 2020 18:37:58 -0500 Subject: [PATCH 0468/1582] v5.9.1 changelog --- 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 6cb588bfd..299f1b8ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log +## [5.9.1] - 2020-09-15 +https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + +- Fix the ha option for firewalls, add and implement unit test #1327 +- BluePages_Search and IntegratedOfferingTeam_Region don't need SoftLayer_ prefix #972 +- Fix new TOX issues #1330 +- Add more unit test coverage #1331 +- Set notes for network storage #1322 +- Some improvements to the dns commands #999 + + dns zone-list: added resourceRecordCount, added automatic pagination for large zones + + dns record-list: fixed an issue where a record (like SRV types) that don't have a host would cause the command to fail +- Renamed managers.storage.refresh_dep_dupe to SoftLayer.managers.storage.refresh_dupe #1342 to support the new API method. CLI commands now use this method. + ## [5.9.0] - 2020-08-03 https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 8dd619aa4..23ee96975 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.0' +VERSION = 'v5.9.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 33df75d76..60147ff00 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.9.0', + version='5.9.1', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 3d2e2e40668fb3ce64f40d7a5a8b80f13e99840c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 16 Sep 2020 16:14:36 -0500 Subject: [PATCH 0469/1582] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 23 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..5d10555b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: Bug +assignees: '' + +--- + +> Reminder: No username or APIkeys should be added to these issues, as they are public. + + +**Describe the bug** +A clear and concise description of what the bug is. Include the command you used, make sure to include the `-v` flag, as that information is very helpful. Ex: `slcli -v vs list` + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Version** +Include the output of `slcli --version` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..b8a79ec6f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: New Feature +assignees: '' + +--- + +> REMINDER: Never add usernames or apikeys in these issues, as they are public. + +**What are you trying to do?** +A brief explanation of what you are trying to do. Could be something simple like `slcli vs list` doesn't support a filter you need. Or more complex like recreating some functionality that exists in the cloud.ibm.com portal + +**Screen shots** +If the functionality you want exists in the portal, please add a screenshot so we have a better idea of what you need. + +**Additional context** +Add any other context or screenshots about the feature request here. From 4b4857a07defc26219af6c7d8dd62530c29a1115 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 15:25:47 -0500 Subject: [PATCH 0470/1582] Update item_detail.py updated item-detail table alignment --- SoftLayer/CLI/account/item_detail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/account/item_detail.py b/SoftLayer/CLI/account/item_detail.py index 9af5b4bf2..54175ffe1 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -27,6 +27,7 @@ def item_table(item): table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) 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')) if fqdn != ".": table.add_row(['FQDN', fqdn]) From 2f2af4686e0b4050babb79ac114c70512c658849 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 16:56:09 -0500 Subject: [PATCH 0471/1582] added pylint igmore to a fixture --- SoftLayer/fixtures/SoftLayer_Product_Package.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 15a434c25..79a36e325 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -1,3 +1,4 @@ +# pylint: skip-file HARDWARE_ITEMS = [ {'attributes': [], 'capacity': '999', From 9c05c457325b46598ffa17eeabcdebc2ff303ae8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 17:04:30 -0500 Subject: [PATCH 0472/1582] Update item_detail.py not sure why, but using `table.align` with a KeyValueTable causes a whole bunch of pylint errors.... not really sure why KeyValueTable is a thing anymore. --- 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 54175ffe1..ddc2d31ed 100644 --- a/SoftLayer/CLI/account/item_detail.py +++ b/SoftLayer/CLI/account/item_detail.py @@ -22,7 +22,7 @@ def item_table(item): """Formats a table for billing items""" date_format = '%Y-%m-%d' - table = formatting.KeyValueTable(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) + table = formatting.Table(["Key", "Value"], title="{}".format(item.get('description', 'Billing Item'))) table.add_row(['createDate', utils.clean_time(item.get('createDate'), date_format, date_format)]) table.add_row(['cycleStartDate', utils.clean_time(item.get('cycleStartDate'), date_format, date_format)]) table.add_row(['cancellationDate', utils.clean_time(item.get('cancellationDate'), date_format, date_format)]) From 8a873b002f1e3fb6d4afffbae6c51612f769eddf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 17 Sep 2020 17:19:51 -0500 Subject: [PATCH 0473/1582] Update README.rst updated snapcraft badge --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b4a321866..4f741a5d0 100644 --- a/README.rst +++ b/README.rst @@ -15,8 +15,8 @@ SoftLayer API Python Client .. image:: https://coveralls.io/repos/github/softlayer/softlayer-python/badge.svg?branch=master :target: https://coveralls.io/github/softlayer/softlayer-python?branch=master -.. image:: https://build.snapcraft.io/badge/softlayer/softlayer-python.svg - :target: https://build.snapcraft.io/user/softlayer/softlayer-python +.. image:: https://snapcraft.io//slcli/badge.svg + :target: https://snapcraft.io/slcli This library provides a simple Python client to interact with `SoftLayer's From 5122f954517f1e3c00019e0ca590f8fe6d6e97ad Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Sep 2020 09:21:28 -0400 Subject: [PATCH 0474/1582] slcli account orders --- SoftLayer/CLI/account/orders.py | 38 +++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Billing_Order.py | 47 +++++++++++++++++++ SoftLayer/managers/account.py | 25 ++++++++++ tests/CLI/modules/account_tests.py | 7 ++- 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/account/orders.py create mode 100644 SoftLayer/fixtures/SoftLayer_Billing_Order.py diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py new file mode 100644 index 000000000..7129fddb9 --- /dev/null +++ b/SoftLayer/CLI/account/orders.py @@ -0,0 +1,38 @@ +"""Order list account""" +# :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() +@click.option('--limit', '-l', + help='How many results to get in one api call, default is 100', + default=100, + show_default=True) +@environment.pass_env +def cli(env, limit): + """Order list account.""" + manager = AccountManager(env.client) + orders = manager.get_account_all_billing_orders(limit) + + table = [] + order_table = formatting.Table(['id', 'State', 'user', 'PurchaseDate', 'orderTotalAmount', 'Items'], + title="orders") + order_table.sortby = 'orderTotalAmount' + order_table.align = 'l' + + for order in orders: + items = [] + for item in order['items']: + items.append(item['description']) + create_date = utils.clean_time(order['createDate'], in_format='%Y-%m-%d', out_format='%Y-%m-%d') + + order_table.add_row([order['id'], order['status'], order['userRecord']['username'], create_date, + order['orderTotalAmount'], utils.trim_to(' '.join(map(str, items)), 50)]) + table.append(order_table) + env.fout(formatting.listing(table, separator='\n')) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index ff3e1c180..2633dc2cd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -20,6 +20,7 @@ ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), ('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'), ('virtual', 'SoftLayer.CLI.virt'), ('virtual:bandwidth', 'SoftLayer.CLI.virt.bandwidth:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py new file mode 100644 index 000000000..f8c5505bc --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -0,0 +1,47 @@ +getAllObjects = [{ + 'accountId': 123456, + 'createDate': '2020-09-15T13:12:08-06:00', + 'id': 112356450, + 'modifyDate': '2020-09-15T13:13:13-06:00', + 'status': 'COMPLETED', + 'userRecordId': 987456321, + 'userRecord': { + 'username': 'test@test.com' + }, + 'items': [ + { + 'categoryCode': 'port_speed', + 'description': '100 Mbps Private Network Uplink' + }, + { + 'categoryCode': 'service_port', + 'description': '100 Mbps Private Uplink' + }, + { + 'categoryCode': 'public_port', + 'description': '0 Mbps Public Uplink' + } + ], + 'orderApprovalDate': '2020-09-15T13:13:13-06:00', + 'orderTotalAmount': '0' +}, + { + 'accountId': 123456, + 'createDate': '2019-09-15T13:12:08-06:00', + 'id': 645698550, + 'modifyDate': '2019-09-15T13:13:13-06:00', + 'status': 'COMPLETED', + 'userRecordId': 987456321, + 'userRecord': { + 'username': 'test@test.com' + }, + 'items': [ + { + 'categoryCode': 'port_speed', + 'description': '100 Mbps Private Network Uplink' + }, + + ], + 'orderApprovalDate': '2019-09-15T13:13:13-06:00', + 'orderTotalAmount': '0' + }] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 56f951c28..ad89f3558 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -233,3 +233,28 @@ def cancel_item(self, identifier, reason="No longer needed", note=None): note = "Cancelled by {} with the SLCLI".format(user.get('username')) return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) + + def get_account_all_billing_orders(self, limit, mask=None): + """Gets all the topLevelBillingItems currently active on the account + + :param string mask: Object Mask + :return: Billing_Item + """ + + if mask is None: + mask = """ + orderTotalAmount, userRecord, + initialInvoice[id,amount,invoiceTotalAmount], + items[description] + """ + object_filter = { + 'createDate': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC'] + }]} + } + + return self.client.call('Billing_Order', 'getAllObjects', + limit=limit, mask=mask, filter=object_filter) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index e231bb2be..9b88e3fae 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -110,4 +110,9 @@ def test_account_get_billing_item_detail(self): def test_account_cancel_item(self): result = self.run_command(['account', 'cancel-item', '12345']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') + self.assert_called_with('SoftLayer_Billing_Item', 'cancelItem', identifier='12345') + + def test_acccount_order(self): + result = self.run_command(['account', 'orders']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order', 'getAllObjects') From 9302f97cb029417267da80a2cb8ad9cd5178af02 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Sep 2020 09:35:34 -0400 Subject: [PATCH 0475/1582] add documentation --- docs/cli/account.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index c34f37d7d..27b4198d5 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -34,4 +34,8 @@ Account Commands .. click:: SoftLayer.CLI.account.cancel_item:cli :prog: account cancel-item + :show-nested: + +.. click:: SoftLayer.CLI.account.orders:cli + :prog: account orders :show-nested: \ No newline at end of file From ad131077fb6823c1e3f958fe44ce256876c28858 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 22 Sep 2020 09:59:14 -0400 Subject: [PATCH 0476/1582] Add to block and file storage volume lis order. --- SoftLayer/CLI/block/list.py | 4 +++- SoftLayer/CLI/file/list.py | 4 +++- SoftLayer/managers/block.py | 7 +++++- SoftLayer/managers/file.py | 7 +++++- tests/CLI/modules/block_tests.py | 21 +++++++++++++++++ tests/CLI/modules/file_tests.py | 20 ++++++++++++++++ tests/managers/block_tests.py | 40 ++++++++++++++++++++++++++++++++ tests/managers/file_tests.py | 40 ++++++++++++++++++++++++++++++++ 8 files changed, 139 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 6df25e13e..d9f4cf4d0 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -58,6 +58,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -68,11 +69,12 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, storage_type): +def cli(env, sortby, columns, datacenter, username, storage_type, order): """List block storage.""" block_manager = SoftLayer.BlockStorageManager(env.client) block_volumes = block_manager.list_block_volumes(datacenter=datacenter, username=username, + order=order, storage_type=storage_type, mask=columns.mask()) diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 8bb782884..5ae320c13 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -56,6 +56,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') +@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -66,11 +67,12 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, storage_type): +def cli(env, sortby, columns, datacenter, username, order, storage_type): """List file storage.""" file_manager = SoftLayer.FileStorageManager(env.client) file_volumes = file_manager.list_file_volumes(datacenter=datacenter, username=username, + order=order, storage_type=storage_type, mask=columns.mask()) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index a16500919..e4de585cf 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -25,9 +25,10 @@ def list_block_volume_limit(self): """ return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): """Returns a list of block volumes. + :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance @@ -67,6 +68,10 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None, _filter['iscsiNetworkStorage']['username'] = \ (utils.query_filter(username)) + if order: + _filter['iscsiNetworkStorage']['billingItem']['orderItem'][ + 'order']['id'] = (utils.query_filter(order)) + kwargs['filter'] = _filter.to_dict() return self.client.call('Account', 'getIscsiNetworkStorage', **kwargs) diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 1810a90dc..30653821c 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -22,9 +22,10 @@ def list_file_volume_limit(self): """ return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): """Returns a list of file volumes. + :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance @@ -64,6 +65,10 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, * _filter['nasNetworkStorage']['username'] = \ (utils.query_filter(username)) + if order: + _filter['nasNetworkStorage']['billingItem']['orderItem'][ + 'order']['id'] = (utils.query_filter(order)) + kwargs['filter'] = _filter.to_dict() return self.client.call('Account', 'getNasNetworkStorage', **kwargs) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index a910f597e..196b4a51e 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -141,6 +141,27 @@ def test_volume_list(self): }], json.loads(result.output)) + def test_volume_list_order(self): + result = self.run_command(['block', 'volume-list', '--order=1234567']) + + self.assert_no_fail(result) + self.assertEqual([ + { + 'bytes_used': None, + 'capacity_gb': 20, + 'datacenter': 'dal05', + 'id': 100, + 'iops': None, + 'ip_addr': '10.1.2.3', + 'lunId': None, + 'notes': "{'status': 'availabl", + 'rep_partner_count': None, + 'storage_type': 'ENDURANCE', + 'username': 'username', + 'active_transactions': None + }], + json.loads(result.output)) + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 2e4839299..cc58e1df0 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -55,6 +55,26 @@ def test_volume_list(self): }], json.loads(result.output)) + def test_volume_list_order(self): + result = self.run_command(['file', 'volume-list', '--order=1234567']) + + self.assert_no_fail(result) + self.assertEqual([ + { + 'bytes_used': None, + 'capacity_gb': 10, + 'datacenter': 'Dallas', + 'id': 1, + 'ip_addr': '127.0.0.1', + 'storage_type': 'ENDURANCE', + 'username': 'user', + 'active_transactions': None, + 'mount_addr': '127.0.0.1:/TEST', + 'notes': None, + 'rep_partner_count': None + }], + json.loads(result.output)) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 54f78fb5e..e603ef7e3 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -157,6 +157,46 @@ def test_list_block_volumes(self): mask='mask[%s]' % expected_mask ) + def test_list_block_volumes_additional_filter_order(self): + result = self.block.list_block_volumes(order=1234567) + + self.assertEqual(SoftLayer_Account.getIscsiNetworkStorage, + result) + + expected_filter = { + 'iscsiNetworkStorage': { + 'storageType': { + 'keyName': {'operation': '*= BLOCK_STORAGE'} + }, + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ ISCSI'} + } + }, + 'billingItem': { + 'orderItem': { + 'order': { + 'id': {'operation': 1234567}}}} + } + } + + expected_mask = 'id,' \ + 'username,' \ + 'lunId,' \ + 'capacityGb,' \ + 'bytesUsed,' \ + 'serviceResource.datacenter[name],' \ + 'serviceResourceBackendIpAddress,' \ + 'activeTransactionCount,' \ + 'replicationPartnerCount' + + self.assert_called_with( + 'SoftLayer_Account', + 'getIscsiNetworkStorage', + filter=expected_filter, + mask='mask[%s]' % expected_mask + ) + def test_list_block_volumes_with_additional_filters(self): result = self.block.list_block_volumes(datacenter="dal09", storage_type="Endurance", diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index ec50ee463..15d64d883 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -365,6 +365,46 @@ def test_list_file_volumes(self): mask='mask[%s]' % expected_mask ) + def test_list_file_volumes_additional_filter_order(self): + result = self.file.list_file_volumes(order=1234567) + + self.assertEqual(SoftLayer_Account.getNasNetworkStorage, + result) + + expected_filter = { + 'nasNetworkStorage': { + 'storageType': { + 'keyName': {'operation': '*= FILE_STORAGE'} + }, + 'serviceResource': { + 'type': { + 'type': {'operation': '!~ NAS'} + } + }, + 'billingItem': { + 'orderItem': { + 'order': { + 'id': {'operation': 1234567}}}} + } + } + + expected_mask = 'id,'\ + 'username,'\ + 'capacityGb,'\ + 'bytesUsed,'\ + 'serviceResource.datacenter[name],'\ + 'serviceResourceBackendIpAddress,'\ + 'activeTransactionCount,'\ + 'fileNetworkMountAddress,'\ + 'replicationPartnerCount' + + self.assert_called_with( + 'SoftLayer_Account', + 'getNasNetworkStorage', + filter=expected_filter, + mask='mask[%s]' % expected_mask + ) + def test_list_file_volumes_with_additional_filters(self): result = self.file.list_file_volumes(datacenter="dal09", storage_type="Endurance", From 43c64a041320fd9f08050d1764f8fd4d6b9cf3cf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 22 Sep 2020 18:14:39 -0400 Subject: [PATCH 0477/1582] Add prices to vs create-options. --- SoftLayer/CLI/virt/create_options.py | 325 ++++++++++++++++++++++----- SoftLayer/managers/vs.py | 103 +++++++-- tests/CLI/modules/vs/vs_tests.py | 8 + tests/managers/hardware_tests.py | 3 - tests/managers/vs/vs_tests.py | 85 ++++++- 5 files changed, 443 insertions(+), 81 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 4e73d55be..c6bca1946 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -9,16 +9,21 @@ @click.command(short_help="Get options to use for creating virtual servers.") +@click.argument('location', required=False) @click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', - type=click.Choice(['TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'PUBLIC_CLOUD_SERVER']), + type=click.Choice(['PUBLIC_CLOUD_SERVER', 'TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', + 'CLOUD_SERVER']), help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " - "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, PUBLIC_CLOUD_SERVER") + "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, CLOUD_SERVER") +@click.option('--prices', '-p', is_flag=True, + help='Use --prices to list the server item prices, and to list the Item Prices by location,' + 'add it to the --prices option using location short name, e.g. --prices dal13') @environment.pass_env -def cli(env, vsi_type): +def cli(env, vsi_type, prices, location=None): """Virtual server order options.""" vsi = SoftLayer.VSManager(env.client) - options = vsi.get_create_options(vsi_type) + options = vsi.get_create_options(vsi_type, location) tables = [] @@ -26,72 +31,290 @@ def cli(env, vsi_type): dc_table = formatting.Table(['datacenter', 'Value'], title="Datacenters") dc_table.sortby = 'Value' dc_table.align = 'l' - - for location in options['locations']: - dc_table.add_row([location['name'], location['key']]) + for location_info in options['locations']: + dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - # Operation system - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' + if prices: + preset_prices_table(options['sizes'], tables) + os_prices_table(options['operating_systems'], tables) + port_speed_prices_table(options['port_speed'], tables) + ram_prices_table(options['ram'], tables) + database_prices_table(options['database'], tables) + guest_core_prices_table(options['guest_core'], tables) + guest_disk_prices_table(options['guest_disk'], tables) + extras_prices_table(options['extras'], tables) + else: + # Operation system + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' + os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) + for operating_system in options['operating_systems']: + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + tables.append(os_table) + + # Sizes + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + preset_table.sortby = 'Value' + preset_table.align = 'l' + + for size in options['sizes']: + preset_table.add_row([size['name'], size['key']]) + tables.append(preset_table) + + # RAM + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' + ram_table.align = 'l' + + for ram in options['ram']: + ram_table.add_row([ram['name'], ram['key']]) + tables.append(ram_table) + + # Data base + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' + database_table.align = 'l' + + for database in options['database']: + database_table.add_row([database['name'], database['key']]) + tables.append(database_table) + + # Guest_core + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' + guest_core_table.align = 'l' + + for guest_core in options['guest_core']: + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + tables.append(guest_core_table) + + # Guest_core + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' + guest_disk_table.align = 'l' + + for guest_disk in options['guest_disk']: + guest_disk_table.add_row( + [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + tables.append(guest_disk_table) - # Sizes - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") + # Port speed + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' + port_speed_table.align = 'l' + + for speed in options['port_speed']: + port_speed_table.add_row([speed['name'], speed['key']]) + tables.append(port_speed_table) + + env.fout(formatting.listing(tables, separator='\n')) + + +def preset_prices_table(sizes, tables): + """Shows Server Preset options prices. + + :param [] sizes: List of Hardware Server sizes. + :param tables: Table formatting. + """ + preset_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") preset_table.sortby = 'Value' preset_table.align = 'l' - - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) + for size in sizes: + preset_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_table) - # RAM - ram_table = formatting.Table(['memory', 'Value'], title="RAM") - ram_table.sortby = 'Value' - ram_table.align = 'l' - for ram in options['ram']: - ram_table.add_row([ram['name'], ram['key']]) +def os_prices_table(operating_systems, tables): + """Shows Server Operating Systems prices cost and capacity restriction. + + :param [] operating_systems: List of Hardware Server operating systems. + :param tables: Table formatting. + """ + os_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems Prices") + os_table.sortby = 'OS Key' + os_table.align = 'l' + for operating_system in operating_systems: + for price in operating_system['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + os_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(os_table) + + +def port_speed_prices_table(port_speeds, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] port_speeds: List of Hardware Server Port Speeds. + :param tables: Table formatting. + """ + port_speed_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], + title="Network Options Prices") + port_speed_table.sortby = 'Speed' + port_speed_table.align = 'l' + for speed in port_speeds: + for price in speed['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + port_speed_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(port_speed_table) + + +def extras_prices_table(extras, tables): + """Shows Server extras prices cost and capacity restriction. + + :param [] extras: List of Hardware Server Extras. + :param tables: Table formatting. + """ + extras_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], + title="Extras Prices") + extras_table.align = 'l' + for extra in extras: + for price in extra['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + extras_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(extras_table) + + +def _location_item_prices(location_prices, tables): + """Get a specific data from HS price. + + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) + location_prices_table.sortby = 'keyName' + location_prices_table.align = 'l' + for price in location_prices: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) + tables.append(location_prices_table) + + +def ram_prices_table(ram_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] ram_list: List of Virtual Server Ram. + :param tables: Table formatting. + """ + ram_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Ram Prices") + ram_table.sortby = 'Key' + ram_table.align = 'l' + for ram in ram_list: + for price in ram['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + ram_table.add_row( + [ram['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(ram_table) - # Data base - database_table = formatting.Table(['database', 'Value'], title="Databases") - database_table.sortby = 'Value' - database_table.align = 'l' - for database in options['database']: - database_table.add_row([database['name'], database['key']]) +def database_prices_table(database_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] database_list: List of Virtual Server database. + :param tables: Table formatting. + """ + database_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Data Base Prices") + database_table.sortby = 'Key' + database_table.align = 'l' + for database in database_list: + for price in database['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + database_table.add_row( + [database['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(database_table) - # Guest_core - guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") - guest_core_table.sortby = 'Value' - guest_core_table.align = 'l' - for guest_core in options['guest_core']: - guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) +def guest_core_prices_table(guest_core_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] guest_core_list: List of Virtual Server guest_core. + :param tables: Table formatting. + """ + guest_core_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Guest Core Prices") + guest_core_table.sortby = 'Key' + guest_core_table.align = 'l' + for guest_core in guest_core_list: + for price in guest_core['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + guest_core_table.add_row( + [guest_core['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(guest_core_table) - # Guest_core - guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") - guest_disk_table.sortby = 'Value' - guest_disk_table.align = 'l' - for guest_disk in options['guest_disk']: - guest_disk_table.add_row([guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) +def guest_disk_prices_table(guest_disk_list, tables): + """Shows Server Port Speeds prices cost and capacity restriction. + + :param [] guest_disk_list: List of Virtual Server guest_disk. + :param tables: Table formatting. + """ + guest_disk_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Guest Disk Prices") + guest_disk_table.sortby = 'Key' + guest_disk_table.align = 'l' + for guest_disk in guest_disk_list: + for price in guest_disk['prices']: + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + guest_disk_table.add_row( + [guest_disk['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(guest_disk_table) - # Port speed - port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") - port_speed_table.sortby = 'Key' - port_speed_table.align = 'l' - for speed in options['port_speed']: - port_speed_table.add_row([speed['name'], speed['key']]) - tables.append(port_speed_table) +def _get_price_data(price, item): + """Get a specific data from HS price. - env.fout(formatting.listing(tables, separator='\n')) + :param price: Hardware Server price. + :param string item: Hardware Server price data. + """ + result = '-' + if item in price: + result = price[item] + return result diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 1ddbdc3e1..1a03db1c6 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -13,11 +13,20 @@ from SoftLayer.decoration import retry from SoftLayer import exceptions +from SoftLayer.exceptions import SoftLayerError +from SoftLayer.managers.hardware import _get_preset_cost +from SoftLayer.managers.hardware import get_item_price from SoftLayer.managers import ordering from SoftLayer import utils LOGGER = logging.getLogger(__name__) +EXTRA_CATEGORIES = ['pri_ipv6_addresses', + 'static_ipv6_addresses', + 'sec_ip_addresses', + 'trusted_platform_module', + 'software_guard_extensions'] + # pylint: disable=no-self-use,too-many-lines @@ -249,9 +258,11 @@ def get_instance(self, instance_id, **kwargs): return self.guest.getObject(id=instance_id, **kwargs) @retry(logger=LOGGER) - def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): + def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER", datacenter=None): """Retrieves the available options for creating a VS. + :param string vsi_type: vs keyName. + :param string datacenter: short name, like dal09 :returns: A dictionary of creation options. Example:: @@ -265,26 +276,45 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): # SUSPEND_CLOUD_SERVER package = self._get_package(vsi_type) + location_group_id = None + if datacenter: + _filter = {"name": {"operation": datacenter}} + _mask = "mask[priceGroups]" + dc_details = self.client.call('SoftLayer_Location', 'getDatacenters', mask=_mask, filter=_filter, limit=1) + if not dc_details: + raise SoftLayerError("Unable to find a datacenter named {}".format(datacenter)) + # A DC will have several price groups, no good way to deal with this other than checking each. + # An item should only belong to one type of price group. + for group in dc_details[0].get('priceGroups', []): + # We only care about SOME of the priceGroups, which all SHOULD start with `Location Group X` + # Hopefully this never changes.... + if group.get('description').startswith('Location'): + location_group_id = group.get('id') + # Locations locations = [] for region in package['regions']: - locations.append({ - 'name': region['location']['location']['longName'], - 'key': region['location']['location']['name'], - }) + if datacenter is None or datacenter == region['location']['location']['name']: + locations.append({ + 'name': region['location']['location']['longName'], + 'key': region['location']['location']['name'], + }) operating_systems = [] database = [] port_speeds = [] guest_core = [] local_disk = [] + extras = [] ram = [] sizes = [] for preset in package['activePresets'] + package['accountRestrictedActivePresets']: sizes.append({ 'name': preset['description'], - 'key': preset['keyName'] + 'key': preset['keyName'], + 'hourlyRecurringFee': _get_preset_cost(preset, package['items'], 'hourly', location_group_id), + 'recurringFee': _get_preset_cost(preset, package['items'], 'monthly', location_group_id) }) for item in package['items']: @@ -294,33 +324,39 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): operating_systems.append({ 'name': item['softwareDescription']['longDescription'], 'key': item['keyName'], - 'referenceCode': item['softwareDescription']['referenceCode'] + 'referenceCode': item['softwareDescription']['referenceCode'], + 'prices': get_item_price(item['prices'], location_group_id) }) # database elif category == 'database': database.append({ 'name': item['description'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'port_speed': port_speeds.append({ 'name': item['description'], - 'key': item['keyName'] + 'speed': item['capacity'], + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'guest_core': guest_core.append({ 'name': item['description'], 'capacity': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category == 'ram': ram.append({ 'name': item['description'], 'capacity': item['capacity'], - 'key': item['keyName'] + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) }) elif category.__contains__('guest_disk'): @@ -328,9 +364,16 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): 'name': item['description'], 'capacity': item['capacity'], 'key': item['keyName'], - 'disk': category + 'disk': category, + 'prices': get_item_price(item['prices'], location_group_id) }) # Extras + elif category in EXTRA_CATEGORIES: + extras.append({ + 'name': item['description'], + 'key': item['keyName'], + 'prices': get_item_price(item['prices'], location_group_id) + }) return { 'locations': locations, @@ -340,22 +383,34 @@ def get_create_options(self, vsi_type="PUBLIC_CLOUD_SERVER"): 'guest_core': guest_core, 'port_speed': port_speeds, 'guest_disk': local_disk, - 'sizes': sizes + 'sizes': sizes, + 'extras': extras, } @retry(logger=LOGGER) def _get_package(self, package_keyname): - """Get the package related to simple hardware ordering.""" - mask = ''' - activePresets, accountRestrictedActivePresets, - items[description, keyName, capacity, - attributes[id, attributeTypeKeyName], - itemCategory[id, categoryCode], - softwareDescription[id, referenceCode, longDescription], prices], - regions[location[location[priceGroups]]]''' - - package_id = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]")['id'] - package = self.client.call('Product_Package', 'getObject', id=package_id, mask=mask) + """Get the package related to simple vs ordering. + + :param string package_keyname: Virtual Server package keyName. + """ + items_mask = 'mask[id,keyName,capacity,description,attributes[id,attributeTypeKeyName],' \ + 'itemCategory[id,categoryCode],softwareDescription[id,referenceCode,longDescription],' \ + 'prices[categories]]' + # The preset prices list will only have default prices. The prices->item->prices will have location specific + presets_mask = 'mask[prices]' + region_mask = 'location[location[priceGroups]]' + package = {'items': [], 'activePresets': [], 'accountRestrictedActivePresets': [], 'regions': []} + package_info = self.ordering_manager.get_package_by_key(package_keyname, mask="mask[id]") + + package['items'] = self.client.call('SoftLayer_Product_Package', 'getItems', + id=package_info.get('id'), mask=items_mask) + package['activePresets'] = self.client.call('SoftLayer_Product_Package', 'getActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['accountRestrictedActivePresets'] = self.client.call('SoftLayer_Product_Package', + 'getAccountRestrictedActivePresets', + id=package_info.get('id'), mask=presets_mask) + package['regions'] = self.client.call('SoftLayer_Product_Package', 'getRegions', + id=package_info.get('id'), mask=region_mask) return package def cancel_instance(self, instance_id): diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index a680e3c72..7fb23b97b 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -317,6 +317,14 @@ def test_create_options(self): result = self.run_command(['vs', 'create-options', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) + def test_create_options_prices(self): + result = self.run_command(['vs', 'create-options', '--prices', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + self.assert_no_fail(result) + + def test_create_options_prices_location(self): + result = self.run_command(['vs', 'create-options', '--prices', 'dal13', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_dns_sync_both(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e5b532647..062cafc30 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -247,9 +247,6 @@ def test_get_create_options_prices_by_location(self): 'recurringFee': 0.0 } - print("---------") - print(options) - self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], extras['prices'][0]['hourlyRecurringFee']) self.assertEqual(options['locations'][0], locations) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index cf701d21f..e858b2577 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -116,9 +116,88 @@ def test_get_instance(self): identifier=100) def test_get_create_options(self): - self.vs.get_create_options() - self.assert_called_with('SoftLayer_Product_Package', 'getObject', - identifier=200) + options = self.vs.get_create_options() + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address'} + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64' + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks' + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + self.assertEqual(options['extras'][0]['key'], extras['key']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['referenceCode'], + operating_systems['referenceCode']) + self.assertEqual(options['port_speed'][0]['name'], port_speeds['name']) + self.assertEqual(options['sizes'][0], sizes) + + def test_get_create_options_prices_by_location(self): + options = self.vs.get_create_options('wdc07') + + extras = {'key': '1_IPV6_ADDRESS', 'name': '1 IPv6 Address', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + locations = {'key': 'wdc07', 'name': 'Washington 7'} + operating_systems = { + 'key': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'name': 'Ubuntu / 14.04-64', + 'referenceCode': 'UBUNTU_14_64', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + + port_speeds = { + 'key': '10', + 'name': '10 Mbps Public & Private Network Uplinks', + 'prices': [ + { + 'hourlyRecurringFee': '0', + 'id': 272, + 'locationGroupId': '', + 'recurringFee': '0', + } + ] + } + sizes = { + 'key': 'M1_64X512X25', + 'name': 'M1.64x512x25', + 'hourlyRecurringFee': 0.0, + 'recurringFee': 0.0 + } + + self.assertEqual(options['extras'][0]['prices'][0]['hourlyRecurringFee'], + extras['prices'][0]['hourlyRecurringFee']) + self.assertEqual(options['locations'][0], locations) + self.assertEqual(options['operating_systems'][0]['prices'][0]['locationGroupId'], + operating_systems['prices'][0]['locationGroupId']) + self.assertEqual(options['port_speed'][0]['prices'][0]['id'], port_speeds['prices'][0]['id']) + self.assertEqual(options['sizes'][0], sizes) def test_cancel_instance(self): result = self.vs.cancel_instance(1) From a8bead5a115ed1142526b5744d08e6c2c4e82cda Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Sep 2020 16:39:26 -0400 Subject: [PATCH 0478/1582] remove the prices equals a zero(0) --- SoftLayer/CLI/hardware/create_options.py | 76 +++++++++++++----------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index d61695be3..d7486ef6a 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,8 +81,9 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) + if ("%.4f" % size['hourlyRecurringFee'] != 0.00) or ("%.4f" % size['recurringFee'] != 0.00): + preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) @@ -98,14 +99,16 @@ def _os_prices_table(operating_systems, tables): os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - os_prices_table.add_row( - [operating_system['key'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + os_prices_table.add_row( + [operating_system['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(os_prices_table) @@ -121,14 +124,16 @@ def _port_speed_prices_table(port_speeds, tables): port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_prices_table.add_row( - [speed['key'], speed['speed'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + port_speed_prices_table.add_row( + [speed['key'], speed['speed'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(port_speed_prices_table) @@ -143,14 +148,16 @@ def _extras_prices_table(extras, tables): extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_prices_table.add_row( - [extra['key'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( + _get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + extras_prices_table.add_row( + [extra['key'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(extras_prices_table) @@ -176,12 +183,13 @@ def _location_item_prices(location_prices, tables): location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) + if (_get_price_data(price, 'hourlyRecurringFee') != '0') or (_get_price_data(price, 'recurringFee') != '0'): + cr_max = _get_price_data(price, 'capacityRestrictionMaximum') + cr_min = _get_price_data(price, 'capacityRestrictionMinimum') + cr_type = _get_price_data(price, 'capacityRestrictionType') + location_prices_table.add_row( + [price['item']['keyName'], price['id'], + _get_price_data(price, 'hourlyRecurringFee'), + _get_price_data(price, 'recurringFee'), + "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) From 51775ba04ca8831ca195751351b147864500bf70 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Sep 2020 16:44:47 -0400 Subject: [PATCH 0479/1582] remove the prices equals a zero(0) --- 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 d7486ef6a..98f27b3a5 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,7 +81,7 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - if ("%.4f" % size['hourlyRecurringFee'] != 0.00) or ("%.4f" % size['recurringFee'] != 0.00): + if ("%.4f" % size['hourlyRecurringFee'] != '0.0000') or ("%.4f" % size['recurringFee'] != '0.0000'): preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) From d68cef3723fa0faae0831884462f557f0170d592 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 25 Sep 2020 09:27:45 -0400 Subject: [PATCH 0480/1582] fix the unit test --- .../fixtures/SoftLayer_Product_Package.py | 30 +++++++++---------- tests/CLI/modules/server_tests.py | 11 ++++--- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index 79a36e325..f705b0edb 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -866,8 +866,8 @@ 'referenceCode': 'REDHAT_5_64' }, 'prices': [{'id': 1122, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -879,8 +879,8 @@ 'description': 'Public & Private Networks', 'itemCategory': {'categoryCode': 'Uplink Port Speeds'}, 'prices': [{'id': 4477, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 26, 'name': 'Uplink Port Speeds', 'categoryCode': 'port_speed'}]}], @@ -921,8 +921,8 @@ 'itemCategory': {'categoryCode': 'Computing Instance'}, 'prices': [{'id': 1144, 'locationGroupId': None, - 'hourlyRecurringFee': 0.0, - 'recurringFee': 0.0, + 'hourlyRecurringFee': 0.10, + 'recurringFee': 0.10, 'categories': [{'id': 80, 'name': 'Computing Instance', 'categoryCode': 'guest_core'}]}], @@ -948,7 +948,7 @@ 'capacity': '1', 'description': '1 GB iSCSI Storage', 'itemCategory': {'categoryCode': 'iscsi'}, - 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 2222, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 1121, @@ -956,7 +956,7 @@ 'capacity': '20', 'description': '20 GB iSCSI snapshot', 'itemCategory': {'categoryCode': 'iscsi_snapshot_space'}, - 'prices': [{'id': 2014, 'hourlyRecurringFee': 0.0}], + 'prices': [{'id': 2014, 'hourlyRecurringFee': 0.10}], }, { 'id': 4440, @@ -964,7 +964,7 @@ 'capacity': '4', 'description': '4 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 4444, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 8880, @@ -972,7 +972,7 @@ 'capacity': '8', 'description': '8 Portable Public IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_pub'}, - 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 8888, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 44400, @@ -980,7 +980,7 @@ 'capacity': '4', 'description': '4 Portable Private IP Addresses', 'itemCategory': {'categoryCode': 'sov_sec_ip_addresses_priv'}, - 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 44441, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, { 'id': 88800, @@ -1012,7 +1012,7 @@ 'capacity': '0', 'description': 'Global IPv6', 'itemCategory': {'categoryCode': 'global_ipv6'}, - 'prices': [{'id': 611, 'hourlyRecurringFee': 0.0, 'recurringFee': 0.0}], + 'prices': [{'id': 611, 'hourlyRecurringFee': 0.10, 'recurringFee': 0.10}], }, {'attributes': [], 'capacity': '0', @@ -1056,7 +1056,7 @@ 'keyName': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', 'prices': [{'accountRestrictions': [], 'currentPriceFlag': '', - 'hourlyRecurringFee': '0', + 'hourlyRecurringFee': '0.10', 'id': 37650, "locationGroupId": '', 'itemId': 4702, @@ -1064,8 +1064,8 @@ 'onSaleFlag': '', 'oneTimeFee': '0', 'quantity': '', - 'recurringFee': '0', - 'setupFee': '0', + 'recurringFee': '0.1', + 'setupFee': '0.1', 'sort': 9}], 'softwareDescription': {'id': 1362, 'longDescription': 'Ubuntu / 14.04-64', diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index dc9d70726..75b70c7c2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -367,18 +367,17 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') - self.assertEqual(output[1][0]['Size'], 'M1.64x512x25') + self.assertEqual(output[2][0]['Monthly'], str(0.1)) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') def test_create_options_location(self): result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - self.assertEqual(output[1][0]['Monthly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Hourly'], "%.4f" % 0.0) - self.assertEqual(output[1][0]['Value'], 'M1_64X512X25') + print(output) + self.assertEqual(output[2][0]['Monthly'], str(0.1)) + self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): From 7f410f6ed2855db53a0812615d80da4158bc734f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Sep 2020 16:47:00 -0400 Subject: [PATCH 0481/1582] fix Christopher code review comments --- SoftLayer/CLI/account/orders.py | 8 +++----- SoftLayer/managers/account.py | 14 +++----------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 7129fddb9..7777f2379 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -20,10 +20,9 @@ def cli(env, limit): manager = AccountManager(env.client) orders = manager.get_account_all_billing_orders(limit) - table = [] - order_table = formatting.Table(['id', 'State', 'user', 'PurchaseDate', 'orderTotalAmount', 'Items'], + order_table = formatting.Table(['Id', 'State', 'User', 'Date', 'Amount', 'Item'], title="orders") - order_table.sortby = 'orderTotalAmount' + order_table.sortby = 'Amount' order_table.align = 'l' for order in orders: @@ -34,5 +33,4 @@ def cli(env, limit): order_table.add_row([order['id'], order['status'], order['userRecord']['username'], create_date, order['orderTotalAmount'], utils.trim_to(' '.join(map(str, items)), 50)]) - table.append(order_table) - env.fout(formatting.listing(table, separator='\n')) + env.fout(order_table) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index ad89f3558..e12539161 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -234,7 +234,7 @@ def cancel_item(self, identifier, reason="No longer needed", note=None): return self.client.call('Billing_Item', 'cancelItem', False, True, reason, note, id=identifier) - def get_account_all_billing_orders(self, limit, mask=None): + def get_account_all_billing_orders(self, limit=100, mask=None): """Gets all the topLevelBillingItems currently active on the account :param string mask: Object Mask @@ -247,14 +247,6 @@ def get_account_all_billing_orders(self, limit, mask=None): initialInvoice[id,amount,invoiceTotalAmount], items[description] """ - object_filter = { - 'createDate': { - 'operation': 'orderBy', - 'options': [{ - 'name': 'sort', - 'value': ['DESC'] - }]} - } - + print(limit) return self.client.call('Billing_Order', 'getAllObjects', - limit=limit, mask=mask, filter=object_filter) + limit=limit, mask=mask) From 5dc7e95b3e76245831d23baf21d8984d8678de70 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Sep 2020 17:57:24 -0400 Subject: [PATCH 0482/1582] fix the last Christopher code review comments --- SoftLayer/CLI/account/orders.py | 3 +-- SoftLayer/managers/account.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 7777f2379..570f77816 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -11,7 +11,7 @@ @click.command() @click.option('--limit', '-l', - help='How many results to get in one api call, default is 100', + help='How many results to get in one api call', default=100, show_default=True) @environment.pass_env @@ -22,7 +22,6 @@ def cli(env, limit): order_table = formatting.Table(['Id', 'State', 'User', 'Date', 'Amount', 'Item'], title="orders") - order_table.sortby = 'Amount' order_table.align = 'l' for order in orders: diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e12539161..c46872e01 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -247,6 +247,5 @@ def get_account_all_billing_orders(self, limit=100, mask=None): initialInvoice[id,amount,invoiceTotalAmount], items[description] """ - print(limit) return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) From a61b4beea2b73785bac3dc779d38227a33c34041 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 15:55:28 -0400 Subject: [PATCH 0483/1582] #1340 add order lookup --- SoftLayer/CLI/account/invoice_detail.py | 24 ++++++--- SoftLayer/CLI/order/lookup.py | 67 +++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/managers/ordering.py | 23 ++++++++- 4 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 SoftLayer/CLI/order/lookup.py diff --git a/SoftLayer/CLI/account/invoice_detail.py b/SoftLayer/CLI/account/invoice_detail.py index b840f3f60..1fd7e2d5f 100644 --- a/SoftLayer/CLI/account/invoice_detail.py +++ b/SoftLayer/CLI/account/invoice_detail.py @@ -19,6 +19,22 @@ def cli(env, identifier, details): manager = AccountManager(env.client) top_items = manager.get_billing_items(identifier) + table = get_invoice_table(identifier, top_items, details) + env.fout(table) + + +def nice_string(ugly_string, limit=100): + """Format and trims strings""" + return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string + + +def get_invoice_table(identifier, top_items, details): + """Formats a table for invoice top level items. + + :param int identifier: Invoice identifier. + :param list top_items: invoiceTopLevelItems. + :param bool details: To add very detailed list of charges. + """ title = "Invoice %s" % identifier table = formatting.Table(["Item Id", "Category", "Description", "Single", @@ -52,10 +68,4 @@ def cli(env, identifier, details): '---', '---' ]) - - env.fout(table) - - -def nice_string(ugly_string, limit=100): - """Format and trims strings""" - return (ugly_string[:limit] + '..') if len(ugly_string) > limit else ugly_string + return table diff --git a/SoftLayer/CLI/order/lookup.py b/SoftLayer/CLI/order/lookup.py new file mode 100644 index 000000000..423872ab7 --- /dev/null +++ b/SoftLayer/CLI/order/lookup.py @@ -0,0 +1,67 @@ +"""Provides some details related to the order.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI.account.invoice_detail import get_invoice_table + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@click.option('--details', is_flag=True, default=False, show_default=True, + help="Shows a very detailed list of charges") +@environment.pass_env +def cli(env, identifier, details): + """Provides some details related to order owner, date order, cost information, initial invoice.""" + + manager = ordering.OrderingManager(env.client) + order = manager.get_order_detail(identifier) + order_table = get_order_table(order) + + invoice = order.get('initialInvoice', {}) + top_items = invoice.get('invoiceTopLevelItems', []) + invoice_id = invoice.get('id') + invoice_table = get_invoice_table(invoice_id, top_items, details) + + order_table.add_row(['Initial Invoice', invoice_table]) + + env.fout(order_table) + + +def get_order_table(order): + """Formats a table for billing order""" + + title = "Order {id}".format(id=order.get('id')) + date_format = '%Y-%m-%d' + table = formatting.Table(["Key", "Value"], title=title) + table.align = 'l' + + ordered_by = "IBM" + user = order.get('userRecord', None) + if user: + ordered_by = "{} ({})".format(user.get('displayName'), utils.lookup(user, 'userStatus', 'name')) + table.add_row(['Ordered By', ordered_by]) + + table.add_row(['Create Date', utils.clean_time(order.get('createDate'), date_format, date_format)]) + table.add_row(['Modify Date', utils.clean_time(order.get('modifyDate'), date_format, date_format)]) + table.add_row(['Order Approval Date', utils.clean_time(order.get('orderApprovalDate'), date_format, date_format)]) + table.add_row(['status', order.get('status')]) + table.add_row(['Order Total Amount', "{price:.2f}".format(price=float(order.get('orderTotalAmount', '0')))]) + table.add_row(['Invoice Total Amount', "{price:.2f}". + format(price=float(order.get('initialInvoice', {}).get('invoiceTotalAmount', '0')))]) + + items = order.get('items', []) + item_table = formatting.Table(["Item Description"]) + item_table.align['description'] = 'l' + + for item in items: + item_table.add_row([item.get('description')]) + + table.add_row(['items', item_table]) + + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2633dc2cd..40c4bd6d1 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -226,6 +226,7 @@ ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), ('order:quote', 'SoftLayer.CLI.order.quote:cli'), + ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), ('rwhois', 'SoftLayer.CLI.rwhois'), ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 866ee8b43..ad814aff3 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -11,7 +11,6 @@ from SoftLayer import exceptions - CATEGORY_MASK = '''id, isRequired, itemCategory[id, name, categoryCode]''' ITEM_MASK = '''id, keyName, description, itemCategory, categories, prices''' @@ -61,6 +60,28 @@ def get_packages_of_type(self, package_types, mask=None): packages = self.filter_outlet_packages(packages) return packages + def get_order_detail(self, order_id, mask=None): + """Get order details. + + :param int order_id: to specify the order that we want to retrieve. + :param string mask: Mask to specify the properties we want to retrieve. + """ + _default_mask = ( + 'mask[orderTotalAmount,orderApprovalDate,' + 'initialInvoice[id,amount,invoiceTotalAmount,' + 'invoiceTopLevelItems[id, description, hostName, domainName, oneTimeAfterTaxAmount,' + 'recurringAfterTaxAmount, createDate,' + 'categoryCode,' + 'category[name],' + 'location[name],' + 'children[id, category[name], description, oneTimeAfterTaxAmount,recurringAfterTaxAmount]]],' + 'items[description],userRecord[displayName,userStatus]]') + + mask = _default_mask if mask is None else mask + + order = self.billing_svc.getObject(mask=mask, id=order_id) + return order + @staticmethod def filter_outlet_packages(packages): """Remove packages designated as OUTLET. From 273463594179cd05b296a0cb87086ce9c2dd0833 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 15:55:51 -0400 Subject: [PATCH 0484/1582] update from origin --- SoftLayer/fixtures/SoftLayer_Billing_Order.py | 25 +++++++++++++++++ tests/CLI/modules/order_tests.py | 7 +++++ tests/managers/ordering_tests.py | 27 ++++++++++++++++--- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/fixtures/SoftLayer_Billing_Order.py index f8c5505bc..ae35280ea 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order.py @@ -45,3 +45,28 @@ 'orderApprovalDate': '2019-09-15T13:13:13-06:00', 'orderTotalAmount': '0' }] + +getObject = { + 'accountId': 1234, + 'createDate': '2020-09-23T16:22:30-06:00', + 'id': 6543210, + 'impersonatingUserRecordId': None, + 'initialInvoice': { + 'amount': '0', + 'id': 60012345, + 'invoiceTotalAmount': '0' + }, + 'items': [{ + 'description': 'Dual Intel Xeon Silver 4210 (20 Cores, 2.20 GHz)' + }], + 'modifyDate': '2020-09-23T16:22:32-06:00', + 'orderQuoteId': None, + 'orderTypeId': 11, + 'presaleEventId': None, + 'privateCloudOrderFlag': False, + 'status': 'APPROVED', + 'userRecord': { + 'displayName': 'testUser' + }, + 'userRecordId': 7654321, +} diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 13ccb0f80..279f49f5a 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -406,6 +406,13 @@ def _get_verified_order_return(self): 'recurringFee': '150'} return {'orderContainers': [{'prices': [price1, price2]}]} + def test_order_lookup(self): + result = self.run_command(['order', 'lookup', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier='12345') + self.assertIn('Ordered By', result.output) + self.assertIn('Initial Invoice', result.output) + def _get_all_packages(): package_type = {'keyName': 'BARE_METAL_CPU'} diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 675ac290d..8b6dc8beb 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -731,7 +731,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -748,7 +748,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -766,7 +766,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -805,3 +805,24 @@ def test_get_item_prices_by_location(self): self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) + + def test_get_oder_detail_mask(self): + order_id = 12345 + test_mask = 'mask[id]' + self.ordering.get_order_detail(order_id, mask=test_mask) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=test_mask) + + def test_get_oder_detail_default_mask(self): + order_id = 12345 + _default_mask = ( + 'mask[orderTotalAmount,orderApprovalDate,' + 'initialInvoice[id,amount,invoiceTotalAmount,' + 'invoiceTopLevelItems[id, description, hostName, domainName, oneTimeAfterTaxAmount,' + 'recurringAfterTaxAmount, createDate,' + 'categoryCode,' + 'category[name],' + 'location[name],' + 'children[id, category[name], description, oneTimeAfterTaxAmount,recurringAfterTaxAmount]]],' + 'items[description],userRecord[displayName,userStatus]]') + self.ordering.get_order_detail(order_id) + self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=_default_mask) From c9b38745cb1733e867aee4cbb4b57f83d1e51768 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 24 Sep 2020 16:31:29 -0400 Subject: [PATCH 0485/1582] #1340 add order lookup doc --- docs/cli/ordering.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index acaf3a07e..1351cfd9c 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -135,3 +135,9 @@ Quotes .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: + +Lookup +====== +.. click:: SoftLayer.CLI.order.lookup:cli + :prog: order lookup + :show-nested: From e00b921116674b4f09489c2c03adc8fdc16a23f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Sep 2020 12:23:16 -0400 Subject: [PATCH 0486/1582] fix Christopher code review --- SoftLayer/CLI/hardware/create_options.py | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 98f27b3a5..b1bcf9a21 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -81,7 +81,7 @@ def _preset_prices_table(sizes, tables): preset_prices_table.sortby = 'Value' preset_prices_table.align = 'l' for size in sizes: - if ("%.4f" % size['hourlyRecurringFee'] != '0.0000') or ("%.4f" % size['recurringFee'] != '0.0000'): + if (_verify_prices("%.4f" % size['hourlyRecurringFee'])) or (_verify_prices("%.4f" % size['recurringFee'])): preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], "%.4f" % size['recurringFee']]) tables.append(preset_prices_table) @@ -99,8 +99,8 @@ def _os_prices_table(operating_systems, tables): os_prices_table.align = 'l' for operating_system in operating_systems: for price in operating_system['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -124,8 +124,8 @@ def _port_speed_prices_table(port_speeds, tables): port_speed_prices_table.align = 'l' for speed in port_speeds: for price in speed['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -137,6 +137,19 @@ def _port_speed_prices_table(port_speeds, tables): tables.append(port_speed_prices_table) +def _verify_prices(prices): + """Verify the prices is higher to zero(0) or is '-'. + + param prices: value to verify. + Returns: true false. + + """ + if prices == '-': + return True + else: + return float(prices) > 0 + + def _extras_prices_table(extras, tables): """Shows Server extras prices cost and capacity restriction. @@ -148,8 +161,8 @@ def _extras_prices_table(extras, tables): extras_prices_table.align = 'l' for extra in extras: for price in extra['prices']: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or ( - _get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') @@ -183,7 +196,8 @@ def _location_item_prices(location_prices, tables): location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - if (_get_price_data(price, 'hourlyRecurringFee') != '0') or (_get_price_data(price, 'recurringFee') != '0'): + if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( + _verify_prices(_get_price_data(price, 'recurringFee'))): cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') From e3a13379ff48affaaad1be72785ce58ce6b42103 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Sep 2020 17:14:30 -0500 Subject: [PATCH 0487/1582] fixing a few documentation warnings --- SoftLayer/CLI/block/refresh.py | 5 ++++- SoftLayer/CLI/ticket/create.py | 9 ++++----- SoftLayer/CLI/ticket/update.py | 8 +++----- docs/cli/block.rst | 4 ++-- docs/cli/file.rst | 4 ++-- docs/cli/tickets.rst | 11 ++++++----- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 369a6c815..11d21fe3f 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,7 +11,10 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """Refresh a duplicate volume with a snapshot from its parent.""" + """Refresh a duplicate volume with a snapshot from its parent. + + + """ block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) diff --git a/SoftLayer/CLI/ticket/create.py b/SoftLayer/CLI/ticket/create.py index 17667d74f..143c5f7b1 100644 --- a/SoftLayer/CLI/ticket/create.py +++ b/SoftLayer/CLI/ticket/create.py @@ -25,19 +25,18 @@ def cli(env, title, subject_id, body, hardware_identifier, virtual_identifier, priority): """Create a Infrastructure support ticket. - Example:: - - Will create the ticket with `Some text`. + Will create the ticket with `Some text`.:: slcli ticket create --body="Some text" --subject-id 1522 --hardware 12345 --title "My New Ticket" - Will create the ticket with text from STDIN + Will create the ticket with text from STDIN:: cat sometfile.txt | slcli ticket create --subject-id 1003 --virtual 111111 --title "Reboot Me" - Will open the default text editor, and once closed, use that text to create the ticket + Will open the default text editor, and once closed, use that text to create the ticket:: slcli ticket create --subject-id 1482 --title "Vyatta Questions..." + """ ticket_mgr = SoftLayer.TicketManager(env.client) if body is None: diff --git a/SoftLayer/CLI/ticket/update.py b/SoftLayer/CLI/ticket/update.py index f04d36f94..d7c0e7bc7 100644 --- a/SoftLayer/CLI/ticket/update.py +++ b/SoftLayer/CLI/ticket/update.py @@ -16,17 +16,15 @@ def cli(env, identifier, body): """Adds an update to an existing ticket. - Example:: - - Will update the ticket with `Some text`. +Will update the ticket with `Some text`.:: slcli ticket update 123456 --body="Some text" - Will update the ticket with text from STDIN +Will update the ticket with text from STDIN:: cat sometfile.txt | slcli ticket update 123456 - Will open the default text editor, and once closed, use that text to update the ticket +Will open the default text editor, and once closed, use that text to update the ticket:: slcli ticket update 123456 """ diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 10ec8e6a6..5684b5623 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -112,11 +112,11 @@ Block Commands :show-nested: .. click:: SoftLayer.CLI.block.refresh:cli - :prog block volume-refresh + :prog: block volume-refresh :show-nested: .. click:: SoftLayer.CLI.block.convert:cli - :prog block volume-convert + :prog: block volume-convert :show-nested: .. click:: SoftLayer.CLI.block.subnets.list:cli diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 5af9b65cc..31ceb0332 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -100,11 +100,11 @@ File Commands :show-nested: .. click:: SoftLayer.CLI.file.refresh:cli - :prog file volume-refresh + :prog: file volume-refresh :show-nested: .. click:: SoftLayer.CLI.file.convert:cli - :prog file volume-convert + :prog: file volume-convert :show-nested: .. click:: SoftLayer.CLI.file.snapshot.schedule_list:cli diff --git a/docs/cli/tickets.rst b/docs/cli/tickets.rst index 71ef3192c..bb56aad86 100644 --- a/docs/cli/tickets.rst +++ b/docs/cli/tickets.rst @@ -1,13 +1,14 @@ .. _cli_tickets: Support Tickets -=============== +================= -The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. -These tickets will still show up in your web portal, but for the more unified case management API, -see the `Case Management API `_ +The SoftLayer ticket API is used to create "classic" or Infrastructure Support cases. These tickets will still show up in your web portal, but for the more unified case management API, see the `Case Management API `_ + +.. note:: + + Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. -.. note:: Windows Git-Bash users might run into issues with `ticket create` and `ticket update` if --body isn't used, as it doesn't report that it is a real TTY to python, so the default editor can not be launched. .. click:: SoftLayer.CLI.ticket.create:cli :prog: ticket create From 51f786935d8d7966414922344a6d1f5b6f081424 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 30 Sep 2020 17:14:50 -0500 Subject: [PATCH 0488/1582] #1038 allowed 'NONE' to be used as a location if needed --- SoftLayer/CLI/order/place.py | 49 ++++++++++++-------------------- SoftLayer/managers/ordering.py | 3 ++ tests/managers/ordering_tests.py | 4 +++ 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 6b66fb110..6bf0d437c 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -42,40 +42,26 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, quantity, extras, order_items): """Place or verify an order. - This CLI command is used for placing/verifying an order of the specified package in - the given location (denoted by a datacenter's long name). Orders made via the CLI - can then be converted to be made programmatically by calling - SoftLayer.OrderingManager.place_order() with the same keynames. - - Packages for ordering can be retrieved from `slcli order package-list` - Presets for ordering can be retrieved from `slcli order preset-list` (not all packages - have presets) - - Items can be retrieved from `slcli order item-list`. In order to find required - items for the order, use `slcli order category-list`, and then provide the - --category option for each category code in `slcli order item-list`. - +\b +1. Find the package keyName from `slcli order package-list` +2. Find the location from `slcli order package-locations PUBLIC_CLOUD_SERVER` + If the package does not require a location, use 'NONE' instead. +3. Find the needed items `slcli order item-list PUBLIC_CLOUD_SERVER` + Some packages, like PUBLIC_CLOUD_SERVER need presets, `slcli order preset-list PUBLIC_CLOUD_SERVER` +4. Find the complex type from https://sldn.softlayer.com/reference +5. Use that complex type to fill out any --extras Example:: - # Order an hourly VSI with 4 CPU, 16 GB RAM, 100 GB SAN disk, - # Ubuntu 16.04, and 1 Gbps public & private uplink in dal13 - slcli order place --billing hourly CLOUD_SERVER DALLAS13 \\ - GUEST_CORES_4 \\ - RAM_16_GB \\ - REBOOT_REMOTE_CONSOLE \\ - 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS \\ - BANDWIDTH_0_GB_2 \\ - 1_IP_ADDRESS \\ - GUEST_DISK_100_GB_SAN \\ - OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI \\ - MONITORING_HOST_PING \\ - NOTIFICATION_EMAIL_AND_TICKET \\ - AUTOMATED_NOTIFICATION \\ - UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ - --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ - --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + slcli order place --verify --preset B1_2X8X100 --billing hourly + --complex-type SoftLayer_Container_Product_Order_Virtual_Guest + --extras '{"virtualGuests": [{"hostname": "test", "domain": "ibm.com"}]}' + PUBLIC_CLOUD_SERVER DALLAS13 + BANDWIDTH_0_GB_2 MONITORING_HOST_PING NOTIFICATION_EMAIL_AND_TICKET + OS_DEBIAN_9_X_STRETCH_LAMP_64_BIT 1_IP_ADDRESS 1_IPV6_ADDRESS + 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_REMOTE_CONSOLE + AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT + NESSUS_VULNERABILITY_ASSESSMENT_REPORTING """ manager = ordering.OrderingManager(env.client) @@ -118,3 +104,4 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, table.add_row(['created', order['orderDate']]) table.add_row(['status', order['placedOrder']['status']]) env.fout(table) + diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 866ee8b43..847148a08 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -634,6 +634,9 @@ def get_location_id(self, location): if isinstance(location, int): return location + # Some orders dont require a location, just use 0 + if location.upper() == "NONE": + return 0 mask = "mask[id,name,regions[keyname]]" if match(r'[a-zA-Z]{3}[0-9]{2}', location) is not None: search = {'name': {'operation': location}} diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 675ac290d..0471893f8 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -594,6 +594,10 @@ def test_get_location_id_int(self): dc_id = self.ordering.get_location_id(1234) self.assertEqual(1234, dc_id) + def test_get_location_id_NONE(self): + dc_id = self.ordering.get_location_id("NONE") + self.assertEqual(0, dc_id) + def test_location_group_id_none(self): # RestTransport uses None for empty locationGroupId category1 = {'categoryCode': 'cat1'} From e72ee794cf3c46f86423777d62f0f632d7dc8e41 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 1 Oct 2020 13:33:30 -0500 Subject: [PATCH 0489/1582] fixed tox errors --- SoftLayer/CLI/order/place.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index 6bf0d437c..f0ceed350 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -43,7 +43,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, """Place or verify an order. \b -1. Find the package keyName from `slcli order package-list` +1. Find the package keyName from `slcli order package-list` 2. Find the location from `slcli order package-locations PUBLIC_CLOUD_SERVER` If the package does not require a location, use 'NONE' instead. 3. Find the needed items `slcli order item-list PUBLIC_CLOUD_SERVER` @@ -53,7 +53,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, Example:: - slcli order place --verify --preset B1_2X8X100 --billing hourly + slcli order place --verify --preset B1_2X8X100 --billing hourly --complex-type SoftLayer_Container_Product_Order_Virtual_Guest --extras '{"virtualGuests": [{"hostname": "test", "domain": "ibm.com"}]}' PUBLIC_CLOUD_SERVER DALLAS13 @@ -104,4 +104,3 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, table.add_row(['created', order['orderDate']]) table.add_row(['status', order['placedOrder']['status']]) env.fout(table) - From 481c9e5254449e3d6735bfaccf38645761279749 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 1 Oct 2020 13:48:01 -0500 Subject: [PATCH 0490/1582] Update refresh.py fixed typo --- SoftLayer/CLI/block/refresh.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SoftLayer/CLI/block/refresh.py b/SoftLayer/CLI/block/refresh.py index 11d21fe3f..369a6c815 100644 --- a/SoftLayer/CLI/block/refresh.py +++ b/SoftLayer/CLI/block/refresh.py @@ -11,10 +11,7 @@ @click.argument('snapshot_id') @environment.pass_env def cli(env, volume_id, snapshot_id): - """Refresh a duplicate volume with a snapshot from its parent. - - - """ + """Refresh a duplicate volume with a snapshot from its parent.""" block_manager = SoftLayer.BlockStorageManager(env.client) resp = block_manager.refresh_dupe(volume_id, snapshot_id) From 157f6241a98528fb866325852a0f832a227b3129 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 16:22:58 -0400 Subject: [PATCH 0491/1582] Refactor block and file volume list and volume order. --- SoftLayer/CLI/block/list.py | 4 ++-- SoftLayer/CLI/block/order.py | 4 +++- SoftLayer/CLI/file/list.py | 6 +++--- SoftLayer/CLI/file/order.py | 3 +++ SoftLayer/managers/block.py | 4 ++-- SoftLayer/managers/file.py | 4 ++-- tests/CLI/modules/block_tests.py | 30 +++++++++++------------------- tests/CLI/modules/file_tests.py | 29 +++++++++++------------------ 8 files changed, 37 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index d9f4cf4d0..44489f928 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -58,7 +58,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') -@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') +@click.option('--order', '-o', type=int, help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -74,8 +74,8 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): block_manager = SoftLayer.BlockStorageManager(env.client) block_volumes = block_manager.list_block_volumes(datacenter=datacenter, username=username, - order=order, storage_type=storage_type, + order=order, mask=columns.mask()) table = formatting.Table(columns.columns) diff --git a/SoftLayer/CLI/block/order.py b/SoftLayer/CLI/block/order.py index 738080fd0..fa7c6bcf6 100644 --- a/SoftLayer/CLI/block/order.py +++ b/SoftLayer/CLI/block/order.py @@ -6,7 +6,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions - CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -128,5 +127,8 @@ def cli(env, storage_type, size, iops, tier, os_type, order['placedOrder']['id'])) for item in order['placedOrder']['items']: click.echo(" > %s" % item['description']) + click.echo( + '\nYou may run "slcli block volume-list --order {0}" to find this block volume after it ' + 'is ready.'.format(order['placedOrder']['id'])) else: click.echo("Order could not be placed! Please verify your options and try again.") diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index 5ae320c13..f7c08fe18 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -56,7 +56,7 @@ @click.command() @click.option('--username', '-u', help='Volume username') @click.option('--datacenter', '-d', help='Datacenter shortname') -@click.option('--order', '-o', help='Filter by ID of the order that purchased the block storage') +@click.option('--order', '-o', type=int, help='Filter by ID of the order that purchased the block storage') @click.option('--storage-type', help='Type of storage volume', type=click.Choice(['performance', 'endurance'])) @@ -67,13 +67,13 @@ ', '.join(column.name for column in COLUMNS)), default=','.join(DEFAULT_COLUMNS)) @environment.pass_env -def cli(env, sortby, columns, datacenter, username, order, storage_type): +def cli(env, sortby, columns, datacenter, username, storage_type, order): """List file storage.""" file_manager = SoftLayer.FileStorageManager(env.client) file_volumes = file_manager.list_file_volumes(datacenter=datacenter, username=username, - order=order, storage_type=storage_type, + order=order, mask=columns.mask()) table = formatting.Table(columns.columns) diff --git a/SoftLayer/CLI/file/order.py b/SoftLayer/CLI/file/order.py index e665a088b..1c7584961 100644 --- a/SoftLayer/CLI/file/order.py +++ b/SoftLayer/CLI/file/order.py @@ -115,5 +115,8 @@ def cli(env, storage_type, size, iops, tier, order['placedOrder']['id'])) for item in order['placedOrder']['items']: click.echo(" > %s" % item['description']) + click.echo( + '\nYou may run "slcli file volume-list --order {0}" to find this file volume after it ' + 'is ready.'.format(order['placedOrder']['id'])) else: click.echo("Order could not be placed! Please verify your options and try again.") diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index e4de585cf..10327211c 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -25,13 +25,13 @@ def list_block_volume_limit(self): """ return self.get_volume_count_limits() - def list_block_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): + def list_block_volumes(self, datacenter=None, username=None, storage_type=None, order=None, **kwargs): """Returns a list of block volumes. - :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance + :param order: Volume order id. :param kwargs: :return: Returns a list of block volumes. """ diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index 30653821c..d4d34f002 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -22,13 +22,13 @@ def list_file_volume_limit(self): """ return self.get_volume_count_limits() - def list_file_volumes(self, datacenter=None, username=None, order=None, storage_type=None, **kwargs): + def list_file_volumes(self, datacenter=None, username=None, storage_type=None, order=None, **kwargs): """Returns a list of file volumes. - :param order: Volume order id. :param datacenter: Datacenter short name (e.g.: dal09) :param username: Name of volume. :param storage_type: Type of volume: Endurance or Performance + :param order: Volume order id. :param kwargs: :return: Returns a list of file volumes. """ diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 196b4a51e..f061d36a2 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -145,22 +145,8 @@ def test_volume_list_order(self): result = self.run_command(['block', 'volume-list', '--order=1234567']) self.assert_no_fail(result) - self.assertEqual([ - { - 'bytes_used': None, - 'capacity_gb': 20, - 'datacenter': 'dal05', - 'id': 100, - 'iops': None, - 'ip_addr': '10.1.2.3', - 'lunId': None, - 'notes': "{'status': 'availabl", - 'rep_partner_count': None, - 'storage_type': 'ENDURANCE', - 'username': 'username', - 'active_transactions': None - }], - json.loads(result.output)) + json_result = json.loads(result.output) + self.assertEqual(json_result[0]['id'], 100) @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') def test_volume_count(self, list_mock): @@ -220,7 +206,9 @@ def test_volume_order_performance(self, order_mock): 'Order #478 placed successfully!\n' ' > Performance Storage\n > Block Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli block volume-list --order 478" to find this block volume ' + 'after it is ready.\n') def test_volume_order_endurance_tier_not_given(self): result = self.run_command(['block', 'volume-order', @@ -253,7 +241,9 @@ def test_volume_order_endurance(self, order_mock): 'Order #478 placed successfully!\n' ' > Endurance Storage\n > Block Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli block volume-list --order 478" to find this block volume ' + 'after it is ready.\n') @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') def test_volume_order_order_not_placed(self, order_mock): @@ -302,7 +292,9 @@ def test_volume_order_hourly_billing(self, order_mock): ' > Storage as a Service\n' ' > Block Storage\n' ' > 20 GB Storage Space\n' - ' > 200 IOPS\n') + ' > 200 IOPS\n' + '\nYou may run "slcli block volume-list --order 10983647" to find this block volume ' + 'after it is ready.\n') @mock.patch('SoftLayer.BlockStorageManager.order_block_volume') def test_volume_order_performance_manager_error(self, order_mock): diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index cc58e1df0..dac95e0d8 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -59,21 +59,8 @@ def test_volume_list_order(self): result = self.run_command(['file', 'volume-list', '--order=1234567']) self.assert_no_fail(result) - self.assertEqual([ - { - 'bytes_used': None, - 'capacity_gb': 10, - 'datacenter': 'Dallas', - 'id': 1, - 'ip_addr': '127.0.0.1', - 'storage_type': 'ENDURANCE', - 'username': 'user', - 'active_transactions': None, - 'mount_addr': '127.0.0.1:/TEST', - 'notes': None, - 'rep_partner_count': None - }], - json.loads(result.output)) + json_result = json.loads(result.output) + self.assertEqual(json_result[0]['id'], 1) @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): @@ -243,7 +230,9 @@ def test_volume_order_performance(self, order_mock): 'Order #478 placed successfully!\n' ' > Performance Storage\n > File Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 478" to find this file volume after it is ' + 'ready.\n') def test_volume_order_endurance_tier_not_given(self): result = self.run_command(['file', 'volume-order', @@ -276,7 +265,9 @@ def test_volume_order_endurance(self, order_mock): 'Order #478 placed successfully!\n' ' > Endurance Storage\n > File Storage\n' ' > 0.25 IOPS per GB\n > 20 GB Storage Space\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 478" to find this file volume after it is ' + 'ready.\n') @mock.patch('SoftLayer.FileStorageManager.order_file_volume') def test_volume_order_order_not_placed(self, order_mock): @@ -327,7 +318,9 @@ def test_volume_order_hourly_billing(self, order_mock): ' > File Storage\n' ' > 20 GB Storage Space\n' ' > 0.25 IOPS per GB\n' - ' > 10 GB Storage Space (Snapshot Space)\n') + ' > 10 GB Storage Space (Snapshot Space)\n' + '\nYou may run "slcli file volume-list --order 479" to find this file volume after it is ' + 'ready.\n') @mock.patch('SoftLayer.FileStorageManager.order_file_volume') def test_volume_order_performance_manager_error(self, order_mock): From 8d8102c11da852fdd0b9a871eb998cbf788b39b4 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 1 Oct 2020 17:40:33 -0400 Subject: [PATCH 0492/1582] Refactor file and block commands to use the username resolver --- SoftLayer/CLI/block/access/list.py | 4 +++- SoftLayer/CLI/block/snapshot/list.py | 4 +++- SoftLayer/CLI/block/subnets/list.py | 4 +++- SoftLayer/CLI/file/access/list.py | 4 +++- SoftLayer/CLI/file/snapshot/list.py | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/block/access/list.py b/SoftLayer/CLI/block/access/list.py index b011e263a..4ff77ef25 100644 --- a/SoftLayer/CLI/block/access/list.py +++ b/SoftLayer/CLI/block/access/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer.CLI import storage_utils @@ -21,8 +22,9 @@ def cli(env, columns, sortby, volume_id): """List ACLs.""" block_manager = SoftLayer.BlockStorageManager(env.client) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Volume Id') access_list = block_manager.get_block_volume_access_list( - volume_id=volume_id) + volume_id=resolved_id) table = formatting.Table(columns.columns) table.sortby = sortby diff --git a/SoftLayer/CLI/block/snapshot/list.py b/SoftLayer/CLI/block/snapshot/list.py index b47f5949f..1c2f5c7f8 100644 --- a/SoftLayer/CLI/block/snapshot/list.py +++ b/SoftLayer/CLI/block/snapshot/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -38,8 +39,9 @@ def cli(env, volume_id, sortby, columns): """List block storage snapshots.""" block_manager = SoftLayer.BlockStorageManager(env.client) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Volume Id') snapshots = block_manager.get_block_volume_snapshot_list( - volume_id, + resolved_id, mask=columns.mask() ) diff --git a/SoftLayer/CLI/block/subnets/list.py b/SoftLayer/CLI/block/subnets/list.py index e111de1b3..d7576971a 100644 --- a/SoftLayer/CLI/block/subnets/list.py +++ b/SoftLayer/CLI/block/subnets/list.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -26,7 +27,8 @@ def cli(env, access_id): try: block_manager = SoftLayer.BlockStorageManager(env.client) - subnets = block_manager.get_subnets_in_acl(access_id) + resolved_id = helpers.resolve_id(block_manager.resolve_ids, access_id, 'Volume Id') + subnets = block_manager.get_subnets_in_acl(resolved_id) table = formatting.Table(COLUMNS) for subnet in subnets: diff --git a/SoftLayer/CLI/file/access/list.py b/SoftLayer/CLI/file/access/list.py index cc9997a05..34dd6fa8f 100644 --- a/SoftLayer/CLI/file/access/list.py +++ b/SoftLayer/CLI/file/access/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers from SoftLayer.CLI import storage_utils @@ -21,8 +22,9 @@ def cli(env, columns, sortby, volume_id): """List ACLs.""" file_manager = SoftLayer.FileStorageManager(env.client) + resolved_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'Volume Id') access_list = file_manager.get_file_volume_access_list( - volume_id=volume_id) + volume_id=resolved_id) table = formatting.Table(columns.columns) table.sortby = sortby diff --git a/SoftLayer/CLI/file/snapshot/list.py b/SoftLayer/CLI/file/snapshot/list.py index 494900f06..aa9284014 100644 --- a/SoftLayer/CLI/file/snapshot/list.py +++ b/SoftLayer/CLI/file/snapshot/list.py @@ -6,6 +6,7 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers COLUMNS = [ @@ -38,8 +39,9 @@ def cli(env, volume_id, sortby, columns): """List file storage snapshots.""" file_manager = SoftLayer.FileStorageManager(env.client) + resolved_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'Volume Id') snapshots = file_manager.get_file_volume_snapshot_list( - volume_id, + resolved_id, mask=columns.mask() ) From c709a55685aee5b9173403999c822107dee42f3a Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 1 Oct 2020 18:24:52 -0400 Subject: [PATCH 0493/1582] fix tox analysis --- SoftLayer/CLI/file/snapshot/list.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/file/snapshot/list.py b/SoftLayer/CLI/file/snapshot/list.py index aa9284014..3a02a6b56 100644 --- a/SoftLayer/CLI/file/snapshot/list.py +++ b/SoftLayer/CLI/file/snapshot/list.py @@ -8,7 +8,6 @@ from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers - COLUMNS = [ column_helper.Column('id', ('id',), mask='id'), column_helper.Column('name', ('notes',), mask='notes'), From f1abe1a522693d4445a82f9a1a93c2f382886bf9 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 18:59:21 -0400 Subject: [PATCH 0494/1582] Refactor code review. --- SoftLayer/CLI/virt/create_options.py | 310 ++++++++++++--------------- 1 file changed, 133 insertions(+), 177 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index c6bca1946..462090e13 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -13,8 +13,7 @@ @click.option('--vsi-type', required=False, show_default=True, default='PUBLIC_CLOUD_SERVER', type=click.Choice(['PUBLIC_CLOUD_SERVER', 'TRANSIENT_CLOUD_SERVER', 'SUSPEND_CLOUD_SERVER', 'CLOUD_SERVER']), - help="Display options for a specific virtual server packages, for default is PUBLIC_CLOUD_SERVER, " - "choose between TRANSIENT_CLOUD_SERVER, SUSPEND_CLOUD_SERVER, CLOUD_SERVER") + help="VS keyName type.") @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the Item Prices by location,' 'add it to the --prices option using location short name, e.g. --prices dal13') @@ -35,277 +34,234 @@ def cli(env, vsi_type, prices, location=None): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if prices: - preset_prices_table(options['sizes'], tables) - os_prices_table(options['operating_systems'], tables) - port_speed_prices_table(options['port_speed'], tables) - ram_prices_table(options['ram'], tables) - database_prices_table(options['database'], tables) - guest_core_prices_table(options['guest_core'], tables) - guest_disk_prices_table(options['guest_disk'], tables) - extras_prices_table(options['extras'], tables) + if vsi_type == 'CLOUD_SERVER': + tables.append(guest_core_prices_table(options['guest_core'], prices)) + tables.append(ram_prices_table(options['ram'], prices)) else: - # Operation system - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Sizes - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # RAM - ram_table = formatting.Table(['memory', 'Value'], title="RAM") - ram_table.sortby = 'Value' - ram_table.align = 'l' - - for ram in options['ram']: - ram_table.add_row([ram['name'], ram['key']]) - tables.append(ram_table) - - # Data base - database_table = formatting.Table(['database', 'Value'], title="Databases") - database_table.sortby = 'Value' - database_table.align = 'l' - - for database in options['database']: - database_table.add_row([database['name'], database['key']]) - tables.append(database_table) - - # Guest_core - guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") - guest_core_table.sortby = 'Value' - guest_core_table.align = 'l' - - for guest_core in options['guest_core']: - guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) - tables.append(guest_core_table) - - # Guest_core - guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") - guest_disk_table.sortby = 'Value' - guest_disk_table.align = 'l' - - for guest_disk in options['guest_disk']: - guest_disk_table.add_row( - [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) - tables.append(guest_disk_table) - - # Port speed - port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") - port_speed_table.sortby = 'Key' - port_speed_table.align = 'l' - - for speed in options['port_speed']: - port_speed_table.add_row([speed['name'], speed['key']]) - tables.append(port_speed_table) - - env.fout(formatting.listing(tables, separator='\n')) - - -def preset_prices_table(sizes, tables): + tables.append(preset_prices_table(options['sizes'], prices)) + tables.append(os_prices_table(options['operating_systems'], prices)) + tables.append(port_speed_prices_table(options['port_speed'], prices)) + tables.append(database_prices_table(options['database'], prices)) + tables.append(guest_disk_prices_table(options['guest_disk'], prices)) + tables.append(extras_prices_table(options['extras'], prices)) + + env.fout(tables) + + +def preset_prices_table(sizes, prices=False): """Shows Server Preset options prices. :param [] sizes: List of Hardware Server sizes. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - preset_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_price_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") + preset_price_table.sortby = 'Value' + preset_price_table.align = 'l' + + preset_table = formatting.Table(['Size', 'Value'], title="Sizes") preset_table.sortby = 'Value' preset_table.align = 'l' + for size in sizes: - preset_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) - tables.append(preset_table) + if (size['hourlyRecurringFee'] > 0) or (size['recurringFee'] > 0): + preset_price_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) + preset_table.add_row([size['name'], size['key']]) + if prices: + return preset_price_table + return preset_table -def os_prices_table(operating_systems, tables): +def os_prices_table(operating_systems, prices=False): """Shows Server Operating Systems prices cost and capacity restriction. :param [] operating_systems: List of Hardware Server operating systems. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - os_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], - title="Operating Systems Prices") - os_table.sortby = 'OS Key' + os_price_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems Prices") + os_price_table.sortby = 'OS Key' + os_price_table.align = 'l' + + os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + os_table.sortby = 'Key' os_table.align = 'l' + for operating_system in operating_systems: for price in operating_system['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - os_table.add_row( + os_price_table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(os_table) + os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + if prices: + return os_price_table + return os_table -def port_speed_prices_table(port_speeds, tables): +def port_speed_prices_table(port_speeds, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] port_speeds: List of Hardware Server Port Speeds. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - port_speed_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], - title="Network Options Prices") - port_speed_table.sortby = 'Speed' + port_speed_price_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly'], title="Network Options Prices") + port_speed_price_table.sortby = 'Speed' + port_speed_price_table.align = 'l' + + port_speed_table = formatting.Table(['network', 'Key'], title="Network Options") + port_speed_table.sortby = 'Key' port_speed_table.align = 'l' + for speed in port_speeds: for price in speed['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_table.add_row( + port_speed_price_table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(port_speed_table) + _get_price_data(price, 'recurringFee')]) + port_speed_table.add_row([speed['name'], speed['key']]) + if prices: + return port_speed_price_table + return port_speed_table -def extras_prices_table(extras, tables): +def extras_prices_table(extras, prices=False): """Shows Server extras prices cost and capacity restriction. :param [] extras: List of Hardware Server Extras. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - extras_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], - title="Extras Prices") + extras_price_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly'], title="Extras Prices") + extras_price_table.align = 'l' + + extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") + extras_table.sortby = 'Value' extras_table.align = 'l' + for extra in extras: for price in extra['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_table.add_row( + extras_price_table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(extras_table) - + _get_price_data(price, 'recurringFee')]) + extras_table.add_row([extra['name'], extra['key']]) + if prices: + return extras_price_table + return extras_table -def _location_item_prices(location_prices, tables): - """Get a specific data from HS price. - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) - location_prices_table.sortby = 'keyName' - location_prices_table.align = 'l' - for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(location_prices_table) - - -def ram_prices_table(ram_list, tables): +def ram_prices_table(ram_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] ram_list: List of Virtual Server Ram. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - ram_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Ram Prices") - ram_table.sortby = 'Key' + ram_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Ram Prices") + ram_price_table.sortby = 'Key' + ram_price_table.align = 'l' + + ram_table = formatting.Table(['memory', 'Value'], title="RAM") + ram_table.sortby = 'Value' ram_table.align = 'l' + for ram in ram_list: for price in ram['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - ram_table.add_row( + ram_price_table.add_row( [ram['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(ram_table) + _get_price_data(price, 'recurringFee')]) + ram_table.add_row([ram['name'], ram['key']]) + if prices: + return ram_price_table + return ram_table -def database_prices_table(database_list, tables): +def database_prices_table(database_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] database_list: List of Virtual Server database. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - database_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], title="Data Base Prices") - database_table.sortby = 'Key' + database_price_table.sortby = 'Key' + database_price_table.align = 'l' + + database_table = formatting.Table(['database', 'Value'], title="Databases") + database_table.sortby = 'Value' database_table.align = 'l' + for database in database_list: for price in database['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - database_table.add_row( + database_price_table.add_row( [database['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(database_table) + database_table.add_row([database['name'], database['key']]) + if prices: + return database_price_table + return database_table -def guest_core_prices_table(guest_core_list, tables): +def guest_core_prices_table(guest_core_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] guest_core_list: List of Virtual Server guest_core. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - guest_core_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Guest Core Prices") - guest_core_table.sortby = 'Key' + guest_core_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Guest Core Prices") + guest_core_price_table.sortby = 'Key' + guest_core_price_table.align = 'l' + + guest_core_table = formatting.Table(['cpu', 'Value', 'Capacity'], title="Guest_core") + guest_core_table.sortby = 'Value' guest_core_table.align = 'l' + for guest_core in guest_core_list: for price in guest_core['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - guest_core_table.add_row( + guest_core_price_table.add_row( [guest_core['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(guest_core_table) + _get_price_data(price, 'recurringFee')]) + guest_core_table.add_row([guest_core['name'], guest_core['key'], guest_core['capacity']]) + if prices: + return guest_core_price_table + return guest_core_table -def guest_disk_prices_table(guest_disk_list, tables): +def guest_disk_prices_table(guest_disk_list, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] guest_disk_list: List of Virtual Server guest_disk. - :param tables: Table formatting. + :param prices: Include pricing information or not. """ - guest_disk_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Guest Disk Prices") - guest_disk_table.sortby = 'Key' + guest_disk_price_table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Guest Disk Prices") + guest_disk_price_table.sortby = 'Key' + guest_disk_price_table.align = 'l' + + guest_disk_table = formatting.Table(['guest_disk', 'Value', 'Capacity', 'Disk'], title="Guest_disks") + guest_disk_table.sortby = 'Value' guest_disk_table.align = 'l' + for guest_disk in guest_disk_list: for price in guest_disk['prices']: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - guest_disk_table.add_row( + guest_disk_price_table.add_row( [guest_disk['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(guest_disk_table) + _get_price_data(price, 'recurringFee')]) + guest_disk_table.add_row( + [guest_disk['name'], guest_disk['key'], guest_disk['capacity'], guest_disk['disk']]) + if prices: + return guest_disk_price_table + return guest_disk_table def _get_price_data(price, item): From 2d1e2bc6e2525771bed11b0a9b82a57e32340a19 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 1 Oct 2020 19:05:21 -0400 Subject: [PATCH 0495/1582] Fix tox analysis. --- SoftLayer/CLI/virt/create_options.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create_options.py b/SoftLayer/CLI/virt/create_options.py index 462090e13..a3ee24314 100644 --- a/SoftLayer/CLI/virt/create_options.py +++ b/SoftLayer/CLI/virt/create_options.py @@ -186,8 +186,7 @@ def database_prices_table(database_list, prices=False): :param [] database_list: List of Virtual Server database. :param prices: Include pricing information or not. """ - database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], - title="Data Base Prices") + database_price_table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], title="Data Base Prices") database_price_table.sortby = 'Key' database_price_table.align = 'l' From d1c02450c56c1c24dbd13da8e6e76aa744a287ee Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 2 Oct 2020 14:56:55 -0400 Subject: [PATCH 0496/1582] Fix create subnet static for ipv4 price. --- SoftLayer/managers/network.py | 5 ++++- tests/managers/network_tests.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 1e9296cac..9b713479c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -171,7 +171,10 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, item.get('capacity') == quantity_str, version == 4 or (version == 6 and desc in item['description'])]): - price_id = item['prices'][0]['id'] + if version == 4 and subnet_type == 'static': + price_id = item['prices'][1]['id'] + else: + price_id = item['prices'][0]['id'] break order = { diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 57106ecee..361ea1a61 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -100,6 +100,14 @@ def test_add_subnet_for_ipv4(self): self.assertEqual(fixtures.SoftLayer_Product_Order.verifyOrder, result) + result = self.network.add_subnet('static', + quantity=8, + endpoint_id=1234, + version=4, + test_order=True) + + self.assertEqual(fixtures.SoftLayer_Product_Order.verifyOrder, result) + def test_add_subnet_for_ipv6(self): # Test a public IPv6 order result = self.network.add_subnet('public', From d7254a0d5dc526b0b69c64359e86fc5a2617c78c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Oct 2020 16:51:32 -0500 Subject: [PATCH 0497/1582] #1345 refactored create-options a bit to have less code duplication --- SoftLayer/CLI/hardware/create_options.py | 193 ++++++++--------------- tests/CLI/modules/server_tests.py | 5 +- 2 files changed, 68 insertions(+), 130 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index b1bcf9a21..4c7723fb9 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -30,148 +30,110 @@ def cli(env, prices, location=None): dc_table.add_row([location_info['name'], location_info['key']]) tables.append(dc_table) - if prices: - _preset_prices_table(options['sizes'], tables) - _os_prices_table(options['operating_systems'], tables) - _port_speed_prices_table(options['port_speeds'], tables) - _extras_prices_table(options['extras'], tables) - else: - # Presets - preset_table = formatting.Table(['Size', 'Value'], title="Sizes") - preset_table.sortby = 'Value' - preset_table.align = 'l' - for size in options['sizes']: - preset_table.add_row([size['name'], size['key']]) - tables.append(preset_table) - - # Operating systems - os_table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") - os_table.sortby = 'Key' - os_table.align = 'l' - for operating_system in options['operating_systems']: - os_table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) - tables.append(os_table) - - # Port speed - port_speed_table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") - port_speed_table.sortby = 'Speed' - port_speed_table.align = 'l' - for speed in options['port_speeds']: - port_speed_table.add_row([speed['name'], speed['speed'], speed['key']]) - tables.append(port_speed_table) - - # Extras - extras_table = formatting.Table(['Extra Option', 'Value'], title="Extras") - extras_table.sortby = 'Value' - extras_table.align = 'l' - for extra in options['extras']: - extras_table.add_row([extra['name'], extra['key']]) - tables.append(extras_table) + tables.append(_preset_prices_table(options['sizes'], prices)) + tables.append(_os_prices_table(options['operating_systems'], prices)) + tables.append(_port_speed_prices_table(options['port_speeds'], prices)) + tables.append(_extras_prices_table(options['extras'], prices)) + # since this is multiple tables, this is required for a valid JSON object to be rendered. env.fout(formatting.listing(tables, separator='\n')) -def _preset_prices_table(sizes, tables): +def _preset_prices_table(sizes, prices=False): """Shows Server Preset options prices. :param [] sizes: List of Hardware Server sizes. - :param tables: Table formatting. + :param prices: Create a price table or not """ - preset_prices_table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes Prices") - preset_prices_table.sortby = 'Value' - preset_prices_table.align = 'l' - for size in sizes: - if (_verify_prices("%.4f" % size['hourlyRecurringFee'])) or (_verify_prices("%.4f" % size['recurringFee'])): - preset_prices_table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) - tables.append(preset_prices_table) + if prices: + table = formatting.Table(['Size', 'Value', 'Hourly', 'Monthly'], title="Sizes") + for size in sizes: + if size.get('hourlyRecurringFee', 0) + size.get('recurringFee', 0) + 1 > 0: + table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], + "%.4f" % size['recurringFee']]) + else: + table = formatting.Table(['Size', 'Value'], title="Sizes") + for size in sizes: + table.add_row([size['name'], size['key']]) + table.sortby = 'Value' + table.align = 'l' + return table -def _os_prices_table(operating_systems, tables): +def _os_prices_table(operating_systems, prices=False): """Shows Server Operating Systems prices cost and capacity restriction. :param [] operating_systems: List of Hardware Server operating systems. - :param tables: Table formatting. + :param prices: Create a price table or not """ - os_prices_table = formatting.Table(['OS Key', 'Hourly', 'Monthly', 'Restriction'], - title="Operating Systems Prices") - os_prices_table.sortby = 'OS Key' - os_prices_table.align = 'l' - for operating_system in operating_systems: - for price in operating_system['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): + if prices: + table = formatting.Table(['Key', 'Hourly', 'Monthly', 'Restriction'], + title="Operating Systems") + for operating_system in operating_systems: + for price in operating_system['prices']: cr_max = _get_price_data(price, 'capacityRestrictionMaximum') cr_min = _get_price_data(price, 'capacityRestrictionMinimum') cr_type = _get_price_data(price, 'capacityRestrictionType') - os_prices_table.add_row( + table.add_row( [operating_system['key'], _get_price_data(price, 'hourlyRecurringFee'), _get_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(os_prices_table) + else: + table = formatting.Table(['OS', 'Key', 'Reference Code'], title="Operating Systems") + for operating_system in operating_systems: + table.add_row([operating_system['name'], operating_system['key'], operating_system['referenceCode']]) + + table.sortby = 'Key' + table.align = 'l' + return table -def _port_speed_prices_table(port_speeds, tables): +def _port_speed_prices_table(port_speeds, prices=False): """Shows Server Port Speeds prices cost and capacity restriction. :param [] port_speeds: List of Hardware Server Port Speeds. - :param tables: Table formatting. + :param prices: Create a price table or not """ - port_speed_prices_table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly', 'Restriction'], - title="Network Options Prices") - port_speed_prices_table.sortby = 'Speed' - port_speed_prices_table.align = 'l' - for speed in port_speeds: - for price in speed['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - port_speed_prices_table.add_row( + if prices: + table = formatting.Table(['Key', 'Speed', 'Hourly', 'Monthly'], title="Network Options") + for speed in port_speeds: + for price in speed['prices']: + table.add_row( [speed['key'], speed['speed'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(port_speed_prices_table) - - -def _verify_prices(prices): - """Verify the prices is higher to zero(0) or is '-'. - - param prices: value to verify. - Returns: true false. - - """ - if prices == '-': - return True + _get_price_data(price, 'recurringFee')]) else: - return float(prices) > 0 + table = formatting.Table(['Network', 'Speed', 'Key'], title="Network Options") + for speed in port_speeds: + table.add_row([speed['name'], speed['speed'], speed['key']]) + table.sortby = 'Speed' + table.align = 'l' + return table -def _extras_prices_table(extras, tables): +def _extras_prices_table(extras, prices=False): """Shows Server extras prices cost and capacity restriction. :param [] extras: List of Hardware Server Extras. - :param tables: Table formatting. + :param prices: Create a price table or not """ - extras_prices_table = formatting.Table(['Extra Option Key', 'Hourly', 'Monthly', 'Restriction'], - title="Extras Prices") - extras_prices_table.align = 'l' - for extra in extras: - for price in extra['prices']: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - extras_prices_table.add_row( + if prices: + table = formatting.Table(['Key', 'Hourly', 'Monthly'], title="Extras") + + for extra in extras: + for price in extra['prices']: + table.add_row( [extra['key'], _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(extras_prices_table) + _get_price_data(price, 'recurringFee')]) + else: + table = formatting.Table(['Extra Option', 'Key'], title="Extras") + for extra in extras: + table.add_row([extra['name'], extra['key']]) + table.sortby = 'Key' + table.align = 'l' + return table def _get_price_data(price, item): @@ -184,26 +146,3 @@ def _get_price_data(price, item): if item in price: result = price[item] return result - - -def _location_item_prices(location_prices, tables): - """Get a specific data from HS price. - - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - location_prices_table = formatting.Table(['keyName', 'priceId', 'Hourly', 'Monthly', 'Restriction']) - location_prices_table.sortby = 'keyName' - location_prices_table.align = 'l' - for price in location_prices: - if (_verify_prices(_get_price_data(price, 'hourlyRecurringFee'))) or ( - _verify_prices(_get_price_data(price, 'recurringFee'))): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') - location_prices_table.add_row( - [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), - "%s - %s %s" % (cr_min, cr_max, cr_type)]) - tables.append(location_prices_table) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 75b70c7c2..9b88c81f2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -368,16 +368,15 @@ def test_create_options_prices(self): self.assert_no_fail(result) output = json.loads(result.output) self.assertEqual(output[2][0]['Monthly'], str(0.1)) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[2][0]['Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') def test_create_options_location(self): result = self.run_command(['server', 'create-options', '--prices', 'dal13']) self.assert_no_fail(result) output = json.loads(result.output) - print(output) self.assertEqual(output[2][0]['Monthly'], str(0.1)) - self.assertEqual(output[2][0]['OS Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') + self.assertEqual(output[2][0]['Key'], 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT') @mock.patch('SoftLayer.HardwareManager.place_order') def test_create_server(self, order_mock): From 01064aaba136c968fbb64bdbe6e4630ad5db48ec Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 7 Oct 2020 11:05:12 -0400 Subject: [PATCH 0498/1582] Refactor create subnet static for ipv4 price. --- SoftLayer/managers/network.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 9b713479c..10d462f10 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -164,17 +164,14 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, # item description. price_id = None quantity_str = str(quantity) - package_items = package.getItems(id=0) + package_items = package.getItems(id=0, mask='mask[prices[packageReferences[package[keyName]]]]') for item in package_items: category_code = utils.lookup(item, 'itemCategory', 'categoryCode') if all([category_code == category, item.get('capacity') == quantity_str, version == 4 or (version == 6 and desc in item['description'])]): - if version == 4 and subnet_type == 'static': - price_id = item['prices'][1]['id'] - else: - price_id = item['prices'][0]['id'] + price_id = self.get_subnet_item_price(item, subnet_type, version) break order = { @@ -195,6 +192,24 @@ def add_subnet(self, subnet_type, quantity=None, endpoint_id=None, version=4, else: return self.client['Product_Order'].placeOrder(order) + @staticmethod + def get_subnet_item_price(item, subnet_type, version): + """Get the subnet specific item price id. + + :param version: 4 for IPv4, 6 for IPv6. + :param subnet_type: Type of subnet to add: private, public, global,static. + :param item: Subnet item. + """ + price_id = None + if version == 4 and subnet_type == 'static': + for item_price in item['prices']: + for package_reference in item_price['packageReferences']: + if subnet_type.upper() in package_reference['package']['keyName']: + price_id = item_price['id'] + else: + price_id = item['prices'][0]['id'] + return price_id + def assign_global_ip(self, global_ip_id, target): """Assigns a global IP address to a specified target. From e75b0271b87c2d072af95b3dd1e23e5f58e64b28 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 7 Oct 2020 12:44:37 -0400 Subject: [PATCH 0499/1582] Capitalizing State and Items rows names for order detail table in lookup.py #1340 --- SoftLayer/CLI/order/lookup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/order/lookup.py b/SoftLayer/CLI/order/lookup.py index 423872ab7..6f25953d0 100644 --- a/SoftLayer/CLI/order/lookup.py +++ b/SoftLayer/CLI/order/lookup.py @@ -50,7 +50,7 @@ def get_order_table(order): table.add_row(['Create Date', utils.clean_time(order.get('createDate'), date_format, date_format)]) table.add_row(['Modify Date', utils.clean_time(order.get('modifyDate'), date_format, date_format)]) table.add_row(['Order Approval Date', utils.clean_time(order.get('orderApprovalDate'), date_format, date_format)]) - table.add_row(['status', order.get('status')]) + table.add_row(['Status', order.get('status')]) table.add_row(['Order Total Amount', "{price:.2f}".format(price=float(order.get('orderTotalAmount', '0')))]) table.add_row(['Invoice Total Amount', "{price:.2f}". format(price=float(order.get('initialInvoice', {}).get('invoiceTotalAmount', '0')))]) @@ -62,6 +62,6 @@ def get_order_table(order): for item in items: item_table.add_row([item.get('description')]) - table.add_row(['items', item_table]) + table.add_row(['Items', item_table]) return table From afb20da13977bb09465994756386ef027f03c399 Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 7 Oct 2020 15:29:32 -0400 Subject: [PATCH 0500/1582] mentioning slcli order lookup in account orders doc block. #1340 --- SoftLayer/CLI/account/orders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/account/orders.py b/SoftLayer/CLI/account/orders.py index 570f77816..f1fb5eeb6 100644 --- a/SoftLayer/CLI/account/orders.py +++ b/SoftLayer/CLI/account/orders.py @@ -1,4 +1,4 @@ -"""Order list account""" +"""Lists account orders.""" # :license: MIT, see LICENSE for more details. import click @@ -16,7 +16,7 @@ show_default=True) @environment.pass_env def cli(env, limit): - """Order list account.""" + """Lists account orders. Use `slcli order lookup ` to find more details about a specific order.""" manager = AccountManager(env.client) orders = manager.get_account_all_billing_orders(limit) From ee670202ceb1d2a55d5c0c114878cee7f32b0ee4 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Thu, 8 Oct 2020 15:34:45 -0500 Subject: [PATCH 0501/1582] Update snapcraft.yaml Rebase to Core18 Adopt-info for auto versioning --- snap/snapcraft.yaml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c05b79e30..769a03614 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,10 +1,14 @@ -name: slcli # check to see if it's available -version: '5.5.1+git' # check versioning -summary: Python based SoftLayer API Tool. # 79 char long summary +name: slcli +adopt-info: slcli +summary: Python based SoftLayer API Tool. description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. -grade: stable # must be 'stable' to release into candidate/stable channels -confinement: strict # use 'strict' once you have the right plugs + +license: MIT + +base: core18 +grade: stable +confinement: strict apps: slcli: @@ -17,11 +21,14 @@ apps: - network-bind parts: - my-part: + slcli: source: https://github.com/softlayer/softlayer-python source-type: git plugin: python python-version: python3 + override-pull: | + snapcraftctl pull + snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" build-packages: - python3 From 8cd8846944ef759bbd135a1e543fa925208190dd Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Mon, 12 Oct 2020 21:16:57 -0500 Subject: [PATCH 0502/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bd4f57dc3..80feba5f3 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,5 @@ name: slcli adopt-info: slcli -version: 'git' # will be replaced by a `git describe` based version string description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 5ef877c3dcaf607302441e1054de6c46395a012c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:05:33 -0500 Subject: [PATCH 0503/1582] Update snapcraft.yaml adding summary to snapcraft --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 80feba5f3..d3bafdd57 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -23,6 +23,7 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git + summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 override-pull: | From 8cd344d09af4dc5a6e41b419ffa4ec22122bdf14 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:27:54 -0500 Subject: [PATCH 0504/1582] moved snapcraft readme because apparently you can't build a snap with it in the snap directory for some reason now #1362 --- snap/README.md => README-snapcraft.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename snap/README.md => README-snapcraft.md (100%) diff --git a/snap/README.md b/README-snapcraft.md similarity index 100% rename from snap/README.md rename to README-snapcraft.md From 8b72304e21ad6287932ada2c718c4629a04a3d65 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 10:48:50 -0500 Subject: [PATCH 0505/1582] Update snapcraft.yaml #1326 getting snap to build again --- snap/snapcraft.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d3bafdd57..59f2156b7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,4 +1,6 @@ name: slcli +summary: A CLI tool to interact with the SoftLayer API. +version: 'git' adopt-info: slcli description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. @@ -23,13 +25,9 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git - summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 - override-pull: | - snapcraftctl pull - snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" - + build-packages: - python3 From 03c450477b079ca833985a87d33fcd4e9450d0b9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Oct 2020 11:45:31 -0500 Subject: [PATCH 0506/1582] #1326 fixing up snapcraft file so it builds this time --- snap/snapcraft.yaml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d3bafdd57..593b92020 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,13 +1,12 @@ -name: slcli -adopt-info: slcli +name: slcli # check to see if it's available +version: 'git' # will be replaced by a `git describe` based version string +summary: Python based SoftLayer API Tool. description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. - -license: MIT - -base: core18 -grade: stable +grade: stable confinement: strict +base: core18 +license: MIT apps: slcli: @@ -23,15 +22,11 @@ parts: slcli: source: https://github.com/softlayer/softlayer-python source-type: git - summary: A CLI tool to interact with the SoftLayer API. plugin: python python-version: python3 - override-pull: | - snapcraftctl pull - snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" build-packages: - python3 stage-packages: - - python3 + - python3 \ No newline at end of file From e60f5a199218372b56ba10f9ed71078fd0ff7097 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Tue, 13 Oct 2020 15:22:19 -0500 Subject: [PATCH 0507/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 80feba5f3..98c96111d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,6 @@ name: slcli adopt-info: slcli +summary: A snap for slcli for SoftLayer products and services description: | A command-line interface is also included and can be used to manage various SoftLayer products and services. From 1ae2c95dd0c69da6315bd9c069b6a4879d188735 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 14 Oct 2020 17:13:48 -0500 Subject: [PATCH 0508/1582] 1057 ended up just writing some improved documentation for how to deal with KeyErrors. Due nested results from the API can be, I felt writing a custom result data structure would end up causing more bugs than we would solve. --- docs/api/client.rst | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index c798ac71d..f1692eb6a 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -202,6 +202,42 @@ If you ever need to figure out what exact API call the client is making, you can print(client.transport.print_reproduceable(call)) +Dealing with KeyError Exceptions +-------------------------------- + +One of the pain points in dealing with the SoftLayer API can be handling issues where you expected a property to be returned, but none was. + +The hostname property of a `SoftLayer_Billing_Item `_ is a good example of this. + +For example. + +:: + + # Uses default username and apikey from ~/.softlayer + client = SoftLayer.create_client_from_env() + # iter_call returns a python generator, and only makes another API call when the loop runs out of items. + result = client.iter_call('Account', 'getAllBillingItems', iter=True, mask="mask[id,hostName]") + print("Id, hostname") + for item in result: + # will throw a KeyError: 'hostName' exception on certain billing items that do not have a hostName + print("{}, {}".format(item['id'], item['hostName'])) + +The Solution +^^^^^^^^^^^^ + +Using the python dictionary's `.get() `_ is great for non-nested items. + +:: + print("{}, {}".format(item.get('id'), item.get('hostName'))) + +Otherwise, this SDK provides a util function to do something similar. Each additional argument passed into `utils.lookup` will go one level deeper into the nested dictionary to find the item requested, returning `None` if a KeyError shows up. + +:: + itemId = SoftLayer.utils.lookup(item, 'id') + itemHostname = SoftLayer.utils.lookup(item, 'hostName') + print("{}, {}".format(itemId, itemHostname)) + + API Reference ------------- From 1c951215362d4d602a4572e975cf40e19763a8af Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 15 Oct 2020 16:13:57 -0400 Subject: [PATCH 0509/1582] update from origin #1346 --- SoftLayer/CLI/order/item_list.py | 54 +++++++++------------ SoftLayer/managers/ordering.py | 33 +++++++++++-- tests/CLI/modules/order_tests.py | 26 +++++++++-- tests/managers/ordering_tests.py | 80 ++++++++++++++++++-------------- 4 files changed, 119 insertions(+), 74 deletions(-) diff --git a/SoftLayer/CLI/order/item_list.py b/SoftLayer/CLI/order/item_list.py index d22be8b56..45323f4bd 100644 --- a/SoftLayer/CLI/order/item_list.py +++ b/SoftLayer/CLI/order/item_list.py @@ -13,15 +13,15 @@ @click.command() -@click.argument('location', required=False, nargs=-1, type=click.UNPROCESSED) @click.argument('package_keyname') -@click.option('--keyword', help="A word (or string) used to filter item names.") -@click.option('--category', help="Category code to filter items by") +@click.option('--keyword', '-k', help="A word (or string) used to filter item names.") +@click.option('--category', '-c', help="Category code to filter items by") @click.option('--prices', '-p', is_flag=True, help='Use --prices to list the server item prices, and to list the ' 'Item Prices by location, add it to the --prices option using ' 'location KeyName, e.g. --prices AMSTERDAM02') +@click.argument('location', required=False) @environment.pass_env -def cli(env, location, package_keyname, keyword, category, prices): +def cli(env, package_keyname, keyword, category, prices, location=None): """List package items used for ordering. The item keyNames listed can be used with `slcli order place` to specify @@ -57,14 +57,13 @@ def cli(env, location, package_keyname, keyword, category, prices): if prices: _item_list_prices(categories, sorted_items, tables) if location: - location = location[0] location_prices = manager.get_item_prices_by_location(location, package_keyname) _location_item_prices(location_prices, location, tables) else: table_items_detail = formatting.Table(COLUMNS) - for catname in sorted(categories): - for item in sorted_items[catname]: - table_items_detail.add_row([catname, item['keyName'], item['description'], get_price(item)]) + for category_name in sorted(categories): + for item in sorted_items[category_name]: + table_items_detail.add_row([category_name, item['keyName'], item['description'], get_price(item)]) tables.append(table_items_detail) env.fout(formatting.listing(tables, separator='\n')) @@ -94,13 +93,13 @@ def get_price(item): def _item_list_prices(categories, sorted_items, tables): """Add the item prices cost and capacity restriction to the table""" table_prices = formatting.Table(COLUMNS_ITEM_PRICES) - for catname in sorted(categories): - for item in sorted_items[catname]: + for category in sorted(categories): + for item in sorted_items[category]: for price in item['prices']: if not price.get('locationGroupId'): - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') + cr_max = get_item_price_data(price, 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price, 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price, 'capacityRestrictionType') table_prices.add_row([item['keyName'], price['id'], get_item_price_data(price, 'hourlyRecurringFee'), get_item_price_data(price, 'recurringFee'), @@ -117,33 +116,22 @@ def get_item_price_data(price, item_attribute): def _location_item_prices(location_prices, location, tables): - """Get a specific data from HS price. + """Add a location prices table to tables. - :param price: Hardware Server price. - :param string item: Hardware Server price data. + :param list location_prices : Location prices. + :param string location : Location. + :param list tables: Table list to add location prices table. """ location_prices_table = formatting.Table(COLUMNS_ITEM_PRICES_LOCATION, title="Item Prices for %s" % location) location_prices_table.sortby = 'keyName' location_prices_table.align = 'l' for price in location_prices: - cr_max = _get_price_data(price, 'capacityRestrictionMaximum') - cr_min = _get_price_data(price, 'capacityRestrictionMinimum') - cr_type = _get_price_data(price, 'capacityRestrictionType') + cr_max = get_item_price_data(price, 'capacityRestrictionMaximum') + cr_min = get_item_price_data(price, 'capacityRestrictionMinimum') + cr_type = get_item_price_data(price, 'capacityRestrictionType') location_prices_table.add_row( [price['item']['keyName'], price['id'], - _get_price_data(price, 'hourlyRecurringFee'), - _get_price_data(price, 'recurringFee'), + get_item_price_data(price, 'hourlyRecurringFee'), + get_item_price_data(price, 'recurringFee'), "%s - %s %s" % (cr_min, cr_max, cr_type)]) tables.append(location_prices_table) - - -def _get_price_data(price, item): - """Get a specific data from HS price. - - :param price: Hardware Server price. - :param string item: Hardware Server price data. - """ - result = '-' - if item in price: - result = price[item] - return result diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 7a34c7720..e1cbb6f31 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -669,14 +669,39 @@ def get_location_id(self, location): return datacenter[0]['id'] def get_item_prices_by_location(self, location, package_keyname): - """Returns the hardware server item prices by location. + """Returns the item prices by location. :param string package_keyname: The package for which to get the items. - :param string location: location to get the item prices. + :param string location: location name or keyname to get the item prices. """ - object_mask = "filteredMask[pricingLocationGroup[locations[regions]]]" + object_mask = "filteredMask[pricingLocationGroup[locations]]" + location_name = self.resolve_location_name(location) object_filter = { - "itemPrices": {"pricingLocationGroup": {"locations": {"regions": {"keyname": {"operation": location}}}}}} + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": location_name}}}}} package = self.get_package_by_key(package_keyname) + return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + + def resolve_location_name(self, location_key): + """Resolves a location name using a string location key. + + :param string location_key: A string location used to resolve the location name. + :return: An location name. + """ + + default_region_keyname = 'unknown' + if not location_key or location_key == default_region_keyname: + raise exceptions.SoftLayerError("Invalid location {}".format(location_key)) + + default_regions = [{'keyname': default_region_keyname}] + index_first = 0 + object_mask = "mask[regions]" + locations = self.client.call('SoftLayer_Location', 'getDatacenters', mask=object_mask) + for location in locations: + location_name = location.get('name') + if location_name == location_key: + return location_key + if location.get('regions', default_regions)[index_first].get('keyname') == location_key: + return location_name + raise exceptions.SoftLayerError("Location {} does not exist".format(location_key)) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index 279f49f5a..f09b5aaea 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -46,7 +46,7 @@ def test_item_list(self): self.assertIn('item2', result.output) def test_item_list_prices(self): - result = self.run_command(['order', 'item-list', '--prices', 'package']) + result = self.run_command(['order', 'item-list', 'package', '--prices']) self.assert_no_fail(result) output = json.loads(result.output) @@ -55,8 +55,28 @@ def test_item_list_prices(self): self.assertEqual(output[0][1]['keyName'], 'KeyName015') self.assert_called_with('SoftLayer_Product_Package', 'getItems') - def test_item_list_location(self): - result = self.run_command(['order', 'item-list', '--prices', 'AMSTERDAM02', 'package']) + def test_item_list_location_keyname(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'DALLAS13', ]) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['priceId'], 1144) + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_location_name(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'dal13', ]) + + self.assert_no_fail(result) + output = json.loads(result.output) + self.assertEqual(output[0][0]['Hourly'], 0.0) + self.assertEqual(output[0][1]['keyName'], 'KeyName015') + self.assertEqual(output[0][1]['priceId'], 1144) + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices') + + def test_item_list_category_keyword(self): + result = self.run_command(['order', 'item-list', 'package', '--prices', 'dal13', '-c', 'os', '-k' 'test']) self.assert_no_fail(result) output = json.loads(result.output) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 182438849..ac4ec70fc 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -776,40 +776,6 @@ def test_get_item_capacity_intel(self): self.assertEqual(24, int(item_capacity)) - def test_get_item_prices_by_location(self): - options = self.ordering.get_item_prices_by_location("MONTREAL", "MONTREAL") - item_prices = [ - { - "hourlyRecurringFee": ".093", - "id": 204015, - "recurringFee": "62", - "item": { - "description": "4 x 2.0 GHz or higher Cores", - "id": 859, - "keyName": "GUEST_CORES_4", - }, - "pricingLocationGroup": { - "id": 503, - "locations": [ - { - "id": 449610, - "longName": "Montreal 1", - "name": "mon01", - "regions": [ - { - "description": "MON01 - Montreal", - "keyname": "MONTREAL", - } - ] - } - ] - } - } - ] - - self.assertEqual(options[0]['item']['keyName'], item_prices[0]['item']['keyName']) - self.assertEqual(options[0]['hourlyRecurringFee'], item_prices[0]['hourlyRecurringFee']) - def test_get_oder_detail_mask(self): order_id = 12345 test_mask = 'mask[id]' @@ -830,3 +796,49 @@ def test_get_oder_detail_default_mask(self): 'items[description],userRecord[displayName,userStatus]]') self.ordering.get_order_detail(order_id) self.assert_called_with('SoftLayer_Billing_Order', 'getObject', identifier=order_id, mask=_default_mask) + + def test_get_item_prices_by_location_name(self): + object_mask = "filteredMask[pricingLocationGroup[locations]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": 'dal13'}}}}} + self.ordering.get_item_prices_by_location('dal13', 'TEST') + + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter) + + def test_get_item_prices_by_location_keyname(self): + object_mask = "filteredMask[pricingLocationGroup[locations]]" + object_filter = { + "itemPrices": {"pricingLocationGroup": {"locations": {"name": {"operation": 'dal13'}}}}} + self.ordering.get_item_prices_by_location('DALLAS13', 'TEST') + + self.assert_called_with('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter) + + def test_resolve_location_name(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('DALLAS13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_by_keyname(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('DALLAS13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_by_name(self): + location_name_expected = 'dal13' + object_mask = "mask[regions]" + location_name = self.ordering.resolve_location_name('dal13') + self.assertEqual(location_name, location_name_expected) + self.assert_called_with('SoftLayer_Location', 'getDatacenters', mask=object_mask) + + def test_resolve_location_name_invalid(self): + exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, None) + self.assertIn("Invalid location", str(exc)) + + def test_resolve_location_name_not_exist(self): + exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") + self.assertIn("does not exist", str(exc)) + From 6464fb889a2484d20654ef0b877ebcf8c3baa4bf Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 15 Oct 2020 16:20:53 -0400 Subject: [PATCH 0510/1582] fix tox removing blank line #1346 --- tests/managers/ordering_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index ac4ec70fc..f42532c7b 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -841,4 +841,3 @@ def test_resolve_location_name_invalid(self): def test_resolve_location_name_not_exist(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) - From 0423459f9cab1145e909e22acdd32c30efee66cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 22 Oct 2020 14:15:21 -0500 Subject: [PATCH 0511/1582] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 299f1b8ac..89cabe59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + dns zone-list: added resourceRecordCount, added automatic pagination for large zones + dns record-list: fixed an issue where a record (like SRV types) that don't have a host would cause the command to fail - Renamed managers.storage.refresh_dep_dupe to SoftLayer.managers.storage.refresh_dupe #1342 to support the new API method. CLI commands now use this method. +- #1295 added disk upgrade options for virtual guests ## [5.9.0] - 2020-08-03 https://github.com/softlayer/softlayer-python/compare/v5.8.9...v5.9.0 From 9fa2e40e80167962bcaefed2ea17054be6a9bd88 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Oct 2020 16:30:05 -0400 Subject: [PATCH 0512/1582] #1367 removing NESSUS_VULNERABILITY_ASSESSMENT_REPORTING from examples and docs --- SoftLayer/CLI/order/place.py | 1 - SoftLayer/CLI/order/place_quote.py | 1 - docs/cli/ordering.rst | 1 - 3 files changed, 3 deletions(-) diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py index f0ceed350..531bacee5 100644 --- a/SoftLayer/CLI/order/place.py +++ b/SoftLayer/CLI/order/place.py @@ -61,7 +61,6 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type, OS_DEBIAN_9_X_STRETCH_LAMP_64_BIT 1_IP_ADDRESS 1_IPV6_ADDRESS 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_REMOTE_CONSOLE AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING """ manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/CLI/order/place_quote.py b/SoftLayer/CLI/order/place_quote.py index 28865ff70..351e9427f 100644 --- a/SoftLayer/CLI/order/place_quote.py +++ b/SoftLayer/CLI/order/place_quote.py @@ -62,7 +62,6 @@ def cli(env, package_keyname, location, preset, name, send_email, complex_type, NOTIFICATION_EMAIL_AND_TICKET \\ AUTOMATED_NOTIFICATION \\ UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \\ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \\ --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \\ --complex-type SoftLayer_Container_Product_Order_Virtual_Guest diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 1351cfd9c..7405bdb0e 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -111,7 +111,6 @@ order place NOTIFICATION_EMAIL_AND_TICKET \ AUTOMATED_NOTIFICATION \ UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT \ - NESSUS_VULNERABILITY_ASSESSMENT_REPORTING \ --extras '{"virtualGuests": [{"hostname": "test", "domain": "softlayer.com"}]}' \ --complex-type SoftLayer_Container_Product_Order_Virtual_Guest From 04817cd5baa73c360f72c551ba731b16856cde30 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 26 Oct 2020 16:40:56 -0400 Subject: [PATCH 0513/1582] #1367 removing NESSUS_VULNERABILITY_ASSESSMENT_REPORTING from fixtures and docs vs --- SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 8 -------- docs/cli/vs.rst | 1 - 2 files changed, 9 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index 987fd12d7..ca72350c1 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -606,14 +606,6 @@ "description": "Unlimited SSL VPN Users & 1 PPTP VPN User per account" } }, - { - "hourlyRecurringFee": "0", - "id": 418, - "recurringFee": "0", - "item": { - "description": "Nessus Vulnerability Assessment & Reporting" - } - } ], "quantity": 1, "sourceVirtualGuestId": None, diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 09539a72b..811e38c98 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -104,7 +104,6 @@ datacenter using the command `slcli vs create`. : 0.0 : Email and Ticket : : 0.0 : Automated Reboot from Monitoring : : 0.0 : Unlimited SSL VPN Users & 1 PPTP VPN User per account : - : 0.0 : Nessus Vulnerability Assessment & Reporting : : 0.0 : 2 GB : : 0.0 : 1 x 2.0 GHz or higher Core : : 0.000 : Total hourly cost : From de317d4b8f2b8f55af16014c9f387627a4c047ea Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 12 Nov 2020 17:44:52 -0400 Subject: [PATCH 0514/1582] Remove the `-a` option from `slcli user create` --- SoftLayer/CLI/user/create.py | 16 ++++++++-------- tests/CLI/modules/user_tests.py | 3 +-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index ccfe55955..988496a17 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -29,9 +29,9 @@ "supersedes this template.") @click.option('--template', '-t', default=None, help="A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/") -@click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") +# @click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") @environment.pass_env -def cli(env, username, email, password, from_user, template, api_key): +def cli(env, username, email, password, from_user, template): """Creates a user Users. Remember to set the permissions and access for this new user. @@ -81,13 +81,13 @@ def cli(env, username, email, password, from_user, template, api_key): raise exceptions.CLIAbort("Canceling creation!") result = mgr.create_user(user_template, password) - new_api_key = None - if api_key: - click.secho("Adding API key...", fg='green') - new_api_key = mgr.add_api_authentication_key(result['id']) + # new_api_key = None + # if api_key11: + # click.secho("Adding API key...", fg='green') + # new_api_key = mgr.add_api_authentication_key(result['id']) - table = formatting.Table(['Username', 'Email', 'Password', 'API Key']) - table.add_row([result['username'], result['email'], password, new_api_key]) + table = formatting.Table(['Username', 'Email', 'Password']) + table.add_row([result['username'], result['email'], password]) env.fout(table) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index f16ef1843..b6723a2b2 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -194,9 +194,8 @@ def test_create_user_generate_password_2(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_and_apikey(self, confirm_mock): confirm_mock.return_value = True - result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-a']) + result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_User_Customer', 'addApiAuthenticationKey') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_with_template(self, confirm_mock): From e1d1857a2aace67a26c208f8d2c103625d748027 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 13 Nov 2020 08:44:09 -0400 Subject: [PATCH 0515/1582] Remove the comments lines --- SoftLayer/CLI/user/create.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SoftLayer/CLI/user/create.py b/SoftLayer/CLI/user/create.py index 988496a17..74e30b6e6 100644 --- a/SoftLayer/CLI/user/create.py +++ b/SoftLayer/CLI/user/create.py @@ -29,7 +29,6 @@ "supersedes this template.") @click.option('--template', '-t', default=None, help="A json string describing https://softlayer.github.io/reference/datatypes/SoftLayer_User_Customer/") -# @click.option('--api-key', '-a', default=False, is_flag=True, help="Create an API key for this user.") @environment.pass_env def cli(env, username, email, password, from_user, template): """Creates a user Users. @@ -81,10 +80,6 @@ def cli(env, username, email, password, from_user, template): raise exceptions.CLIAbort("Canceling creation!") result = mgr.create_user(user_template, password) - # new_api_key = None - # if api_key11: - # click.secho("Adding API key...", fg='green') - # new_api_key = mgr.add_api_authentication_key(result['id']) table = formatting.Table(['Username', 'Email', 'Password']) table.add_row([result['username'], result['email'], password]) From fd082957a8d1e61c9b068dd68b5830b292b938ea Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 13 Nov 2020 11:12:13 -0400 Subject: [PATCH 0516/1582] Fix subnet list. --- SoftLayer/managers/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 10d462f10..11ed9733e 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -22,6 +22,7 @@ DEFAULT_SUBNET_MASK = ','.join(['hardware', 'datacenter', + 'networkVlanId', 'ipAddressCount', 'virtualGuests', 'id', From cda1d46073eac16a2f110f8bc5c5261aff77c716 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 16 Nov 2020 15:15:24 -0600 Subject: [PATCH 0517/1582] #1378 fixed analysis/flake8 tests --- tests/CLI/modules/config_tests.py | 10 +++++----- tests/CLI/modules/vs/vs_create_tests.py | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index ec018a53c..33c82520a 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -67,13 +67,13 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) self.assert_no_fail(result) - self.assertTrue('Configuration Updated Successfully' in result.output) + self.assertIn('Configuration Updated Successfully', result.output) contents = config_file.read().decode("utf-8") - self.assertTrue('[softlayer]' in contents) - self.assertTrue('username = user' in contents) - self.assertTrue('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in contents) - self.assertTrue('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT in contents) + self.assertIn('[softlayer]', contents) + self.assertIn('username = user', contents) + self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) + self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 413bb6c18..53c3bdc97 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -666,8 +666,7 @@ def test_create_vs_export(self): '--domain', 'TESTING', '--flavor', 'B1_2X8X25', '--datacenter', 'TEST00', '--os', 'UBUNTU_LATEST']) self.assert_no_fail(result) - self.assertTrue('Successfully exported options to a template file.' - in result.output) + self.assertIn('Successfully exported options to a template file.', result.output) contents = config_file.read().decode("utf-8") self.assertIn('hostname=TEST', contents) self.assertIn('flavor=B1_2X8X25', contents) From 826c5949089c941332642c99b54bb2daa5b197e4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 18 Nov 2020 09:45:13 -0400 Subject: [PATCH 0518/1582] Add pagination to block and file storage. --- SoftLayer/managers/block.py | 2 +- SoftLayer/managers/file.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/block.py b/SoftLayer/managers/block.py index 10327211c..871c9cb27 100644 --- a/SoftLayer/managers/block.py +++ b/SoftLayer/managers/block.py @@ -73,7 +73,7 @@ def list_block_volumes(self, datacenter=None, username=None, storage_type=None, 'order']['id'] = (utils.query_filter(order)) kwargs['filter'] = _filter.to_dict() - return self.client.call('Account', 'getIscsiNetworkStorage', **kwargs) + return self.client.call('Account', 'getIscsiNetworkStorage', iter=True, **kwargs) def get_block_volume_details(self, volume_id, **kwargs): """Returns details about the specified volume. diff --git a/SoftLayer/managers/file.py b/SoftLayer/managers/file.py index d4d34f002..734e54081 100644 --- a/SoftLayer/managers/file.py +++ b/SoftLayer/managers/file.py @@ -70,7 +70,7 @@ def list_file_volumes(self, datacenter=None, username=None, storage_type=None, o 'order']['id'] = (utils.query_filter(order)) kwargs['filter'] = _filter.to_dict() - return self.client.call('Account', 'getNasNetworkStorage', **kwargs) + return self.client.call('Account', 'getNasNetworkStorage', iter=True, **kwargs) def get_file_volume_details(self, volume_id, **kwargs): """Returns details about the specified volume. From 9cba8fbe82544ab74c6e9ef258be4155d52f8a23 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:24:57 -0600 Subject: [PATCH 0519/1582] Version to 5.9.2 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cabe59d..efa26f9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## [5.9.2] - 2020-12-03 +https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 + +## New Commands +- `slcli account orders` #1349 +- `slcli order lookup` #1354 + +## Improvements +- Ordering price information improvements. #1319 +- refactor vsi create-option #1337 +- Add Invoice Item id as parameter in `slcli account item-detail` command +- Added order lookup command to block and file orders. #1350 +- Add prices to vs create-options. #1351 +- Allow orders without a location if needed #1356 +- Refactor file and block commands to use the username resolver #1357 +- Fix create subnet static for ipv4 price. #1358 +- moved snapcraft readme #1363 +- Update snapcraft.yaml #1365 +- Updated documentation on how to deal with KeyError #1366 +- Fix order item-list --prices location #1360 +- Removed Nessus scanner from docs and examples #1368 +- Fix subnet list. #1379 +- Fixed analysis/flake8 tests #1381 +- Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 + ## [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 23ee96975..a09e85706 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.1' +VERSION = 'v5.9.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 60147ff00..2cb688c44 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name='SoftLayer', - version='5.9.1', + version='5.9.2', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From 2c69e9217b90e0a35c70270b86c3b070b08329d0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:25:53 -0600 Subject: [PATCH 0520/1582] Update CHANGELOG.md Fixed some styling on the changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa26f9c0..abff35816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - `slcli account orders` #1349 - `slcli order lookup` #1354 -## Improvements +#### Improvements - Ordering price information improvements. #1319 - refactor vsi create-option #1337 - Add Invoice Item id as parameter in `slcli account item-detail` command @@ -25,7 +25,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - Fixed analysis/flake8 tests #1381 - Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 -## [5.9.1] - 2020-09-15 +#### [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 - Fix the ha option for firewalls, add and implement unit test #1327 From 367a82e8758d52f0f09909010754865050860eea Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 3 Dec 2020 13:31:14 -0600 Subject: [PATCH 0521/1582] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abff35816..d86588d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [5.9.2] - 2020-12-03 https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 -## New Commands +#### New Commands - `slcli account orders` #1349 - `slcli order lookup` #1354 @@ -25,7 +25,7 @@ https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 - Fixed analysis/flake8 tests #1381 - Remove the `-a` option from `slcli user create`. Only the user themselves can create an API key now. #1377 -#### [5.9.1] - 2020-09-15 +## [5.9.1] - 2020-09-15 https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 - Fix the ha option for firewalls, add and implement unit test #1327 From 1ba743f938ebb159303b22fc47cc065cd46faf0a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 18 Dec 2020 15:11:04 -0600 Subject: [PATCH 0522/1582] Added a unit test for large ints --- ..._Network_Storage_Hub_Cleversafe_Account.py | 9 +++++ tests/transport_tests.py | 34 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index 4bc3f4fc7..4d066cf7e 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -37,3 +37,12 @@ } } ] + +getBuckets = [ + { + "bytesUsed": 40540117, + "name": "normal-bucket", + "objectCount": 4, + "storageLocation": "us-standard" + } +] diff --git a/tests/transport_tests.py b/tests/transport_tests.py index a2e500bd8..27f892098 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -326,6 +326,40 @@ 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') + 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') @pytest.mark.parametrize( From 0f48d23996b7f7239470f730132c353f686afa3e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 15:10:56 +0530 Subject: [PATCH 0523/1582] Adding disaster recovery failover api --- .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ .../replication/disaster_recovery_failover.py | 34 ++++++++++++++++ SoftLayer/CLI/routes.py | 2 + .../fixtures/SoftLayer_Network_Storage.py | 1 + SoftLayer/managers/storage.py | 11 ++++- docs/cli/block.rst | 4 ++ docs/cli/file.rst | 4 ++ tests/CLI/modules/block_tests.py | 39 +++++++++++++++++- tests/CLI/modules/file_tests.py | 40 ++++++++++++++++++- tests/managers/block_tests.py | 12 ++++++ tests/managers/file_tests.py | 12 ++++++ 11 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 SoftLayer/CLI/block/replication/disaster_recovery_failover.py create mode 100644 SoftLayer/CLI/file/replication/disaster_recovery_failover.py diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..cc29fc0ac --- /dev/null +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + block_storage_manager = SoftLayer.BlockStorageManager(env.client) + + click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = block_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py new file mode 100644 index 000000000..7fa02322a --- /dev/null +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -0,0 +1,34 @@ +"""Failover an inaccessible file volume to its available replicant volume.""" +# :license: MIT, see LICENSE for more details. + +import click +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.CLI import exceptions + + +@click.command() +@click.argument('volume-id') +@click.option('--replicant-id', help="ID of the replicant volume") +@environment.pass_env +def cli(env, volume_id, replicant_id): + """Failover an inaccessible file volume to its available replicant volume.""" + file_storage_manager = SoftLayer.FileStorageManager(env.client) + + click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" + """* This action cannot be undone\n""" + """* You will not be able to perform failback to the original without support intervention\n""" + """* You cannot failover without replica""",fg = 'red' ) + + if not (formatting.confirm('Are you sure you want to continue?')): + raise exceptions.CLIAbort('Aborted.') + + success = file_storage_manager.disaster_recovery_failover_to_replicant( + volume_id, + replicant_id + ) + if success: + click.echo("Disaster Recovery Failover to replicant is now in progress.") + else: + click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -92,6 +92,7 @@ ('block:subnets-remove', 'SoftLayer.CLI.block.subnets.remove:cli'), ('block:replica-failback', 'SoftLayer.CLI.block.replication.failback:cli'), ('block:replica-failover', 'SoftLayer.CLI.block.replication.failover:cli'), + ('block:disaster-recovery-failover', 'SoftLayer.CLI.block.replication.disaster_recovery_failover:cli'), ('block:replica-order', 'SoftLayer.CLI.block.replication.order:cli'), ('block:replica-partners', 'SoftLayer.CLI.block.replication.partners:cli'), ('block:replica-locations', 'SoftLayer.CLI.block.replication.locations:cli'), @@ -127,6 +128,7 @@ ('file:access-revoke', 'SoftLayer.CLI.file.access.revoke:cli'), ('file:replica-failback', 'SoftLayer.CLI.file.replication.failback:cli'), ('file:replica-failover', 'SoftLayer.CLI.file.replication.failover:cli'), + ('file:disaster-recovery-failover', 'SoftLayer.CLI.file.replication.disaster_recovery_failover:cli'), ('file:replica-order', 'SoftLayer.CLI.file.replication.order:cli'), ('file:replica-partners', 'SoftLayer.CLI.file.replication.partners:cli'), ('file:replica-locations', 'SoftLayer.CLI.file.replication.locations:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage.py b/SoftLayer/fixtures/SoftLayer_Network_Storage.py index 37297e4d4..bf1f7adc4 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage.py @@ -221,6 +221,7 @@ failoverToReplicant = True failbackFromReplicant = True restoreFromSnapshot = True +disasterRecoveryFailoverToReplicant = True createSnapshot = { 'id': 449 diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 6aceb6f46..89955569c 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -398,13 +398,22 @@ def failover_to_replicant(self, volume_id, replicant_id): """ return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id) + def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): + """Disaster Recovery Failover to a volume replicant. + + :param integer volume_id: The id of the volume + :param integer replicant: ID of replicant to failover to + :return: Returns whether failover to successful or not + """ + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + def failback_from_replicant(self, volume_id): """Failback from a volume replicant. :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - + return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/docs/cli/block.rst b/docs/cli/block.rst index 5684b5623..18a324397 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -142,3 +142,7 @@ Block Commands .. click:: SoftLayer.CLI.block.set_note:cli :prog: block volume-set-note :show-nested: + +.. click:: SoftLayer.CLI.block.replication.disaster_recovery_failover:cli + :prog: block disaster-recovery-failover + :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 31ceb0332..6c914b1a4 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -121,4 +121,8 @@ File Commands .. click:: SoftLayer.CLI.file.set_note:cli :prog: file volume-set-note + :show-nested: + +.. click:: SoftLayer.CLI.file.replication.disaster_recovery_failover:cli + :prog: file disaster-recovery-failover :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..88ad1480b 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerAPIError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -48,7 +49,7 @@ def test_volume_set_lun_id_in_range_missing_value(self): def test_volume_set_lun_id_not_in_range(self): value = '-1' lun_mock = self.set_mock('SoftLayer_Network_Storage', 'createOrUpdateLunId') - lun_mock.side_effect = exceptions.SoftLayerAPIError( + lun_mock.side_effect = SoftLayerAPIError( 'SoftLayer_Exception_Network_Storage_Iscsi_InvalidLunId', 'The LUN ID specified is out of the valid range: %s [min: 0 max: 4095]' % (value)) result = self.run_command('block volume-set-lun-id 1234 42'.split()) @@ -498,6 +499,18 @@ def test_replicant_failover(self): self.assert_no_fail(result) self.assertEqual('Failover to replicant is now in progress.\n', result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -558,6 +571,28 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['block', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dac95e0d8..dc50cd68c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,8 +4,9 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import exceptions +from SoftLayer import SoftLayerError from SoftLayer import testing +from SoftLayer.CLI import exceptions import json import mock @@ -126,7 +127,7 @@ def test_volume_cancel_without_billing_item(self): result = self.run_command([ '--really', 'file', 'volume-cancel', '1234']) - self.assertIsInstance(result.exception, exceptions.SoftLayerError) + self.assertIsInstance(result.exception, SoftLayerError) def test_volume_detail(self): result = self.run_command(['file', 'volume-detail', '1234']) @@ -493,6 +494,41 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = True + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assert_no_fail(result) + self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') + def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): + confirm_mock.return_value = True + disaster_recovery_failover_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + result.output) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_disaster_recovery_failover_aborted(self, confirm_mock): + confirm_mock.return_value = False + + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', + '--replicant-id=5678']) + + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index e603ef7e3..159553db7 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -384,6 +384,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.block.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 15d64d883..91f9325d5 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -289,6 +289,18 @@ def test_replicant_failover(self): identifier=1234, ) + def test_disaster_recovery_failover(self): + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) + def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From bf8c308eee0239accbccdfa1ea8221acb9ebe035 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 6 Jan 2021 22:38:21 +0530 Subject: [PATCH 0524/1582] Refactoring the disaster recovery method --- .../block/replication/disaster_recovery_failover.py | 8 +++----- .../file/replication/disaster_recovery_failover.py | 8 +++----- tests/CLI/modules/block_tests.py | 12 ------------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cc29fc0ac..77b04c65b 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = block_storage_manager.disaster_recovery_failover_to_replicant( + block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 7fa02322a..2e2a946a1 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -24,11 +24,9 @@ def cli(env, volume_id, replicant_id): if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') - success = file_storage_manager.disaster_recovery_failover_to_replicant( + file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - if success: - click.echo("Disaster Recovery Failover to replicant is now in progress.") - else: - click.echo("Disaster Recovery Failover operation could not be initiated.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 88ad1480b..6ccebf66f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -571,18 +571,6 @@ def test_replicant_failover_unsuccessful(self, failover_mock): self.assertEqual('Failover operation could not be initiated.\n', result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['block', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertIn('Disaster Recovery Failover operation could not be initiated.\n', - result.output) - @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): confirm_mock.return_value = False From 47e49e56f4b8f0f9e65dad138145a75fa810350e Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Thu, 7 Jan 2021 22:22:49 +0530 Subject: [PATCH 0525/1582] added warning message and summary for disaster-recover-failover --- .../replication/disaster_recovery_failover.py | 16 ++++++++++------ .../replication/disaster_recovery_failover.py | 14 +++++++++----- tests/CLI/modules/file_tests.py | 14 +------------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index 77b04c65b..cf79cd1a3 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. +To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING:Disaster Recovery Failover a block volume to the given replicant volume.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') @@ -29,4 +33,4 @@ def cli(env, volume_id, replicant_id): replicant_id ) - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 2e2a946a1..3d175d909 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -8,7 +8,10 @@ from SoftLayer.CLI import exceptions -@click.command() +@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. +This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. +After using this method, to fail back to the original volume, please open a support ticket""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -16,10 +19,11 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Disaster Recovery Failover should not be performed unless data center for the primary volume is unreachable.\n""" - """* This action cannot be undone\n""" - """* You will not be able to perform failback to the original without support intervention\n""" - """* You cannot failover without replica""",fg = 'red' ) + click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for fail back via the API.""" + """To fail back to the original volume after using this method, open a support ticket.""" + """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) if not (formatting.confirm('Are you sure you want to continue?')): raise exceptions.CLIAbort('Aborted.') diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index dc50cd68c..b13596355 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -503,19 +503,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) - - @mock.patch('SoftLayer.CLI.formatting.confirm') - @mock.patch('SoftLayer.FileStorageManager.disaster_recovery_failover_to_replicant') - def test_disaster_recovery_failover_unsuccesful(self, disaster_recovery_failover_mock, confirm_mock): - confirm_mock.return_value = True - disaster_recovery_failover_mock.return_value = False - - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) - - self.assertEqual('Disaster Recovery Failover operation could not be initiated.\n', + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') From 9c5af6283ba14766857ddcf4c703775c51fc4bfa Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 7 Jan 2021 18:49:16 -0400 Subject: [PATCH 0526/1582] #1400 get externalBinding and apiAuthenticationKey data to user list --- SoftLayer/managers/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/user.py b/SoftLayer/managers/user.py index 0948df8b3..56acf163e 100644 --- a/SoftLayer/managers/user.py +++ b/SoftLayer/managers/user.py @@ -53,7 +53,7 @@ def list_users(self, objectmask=None, objectfilter=None): if objectmask is None: objectmask = """mask[id, username, displayName, userStatus[name], hardwareCount, virtualGuestCount, - email, roles]""" + email, roles, externalBindingCount,apiAuthenticationKeyCount]""" return self.account_service.getUsers(mask=objectmask, filter=objectfilter) From fd13d77afe4788734d24f3aff8aee547c7bbe15b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 7 Jan 2021 19:00:15 -0400 Subject: [PATCH 0527/1582] #1400 show 2FA and Classic APIs in user list --- SoftLayer/CLI/user/list.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 8e79fe6bb..142f824ab 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -16,7 +16,9 @@ column_helper.Column('displayName', ('displayName',)), column_helper.Column('status', ('userStatus', 'name')), column_helper.Column('hardwareCount', ('hardwareCount',)), - column_helper.Column('virtualGuestCount', ('virtualGuestCount',)) + column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), + column_helper.Column('2FAs', ('externalBindingCount',)), + column_helper.Column('classicAPIKeys', ('apiAuthenticationKeyCount',)) ] DEFAULT_COLUMNS = [ From 687678c1f32235fc099ffdedba3ee030e4c05df7 Mon Sep 17 00:00:00 2001 From: Rajarajan Date: Wed, 13 Jan 2021 18:28:06 +0530 Subject: [PATCH 0528/1582] addressing nitpicky --- .../replication/disaster_recovery_failover.py | 32 ++++++++++--------- .../replication/disaster_recovery_failover.py | 31 ++++++++++-------- SoftLayer/managers/storage.py | 3 +- tests/CLI/modules/block_tests.py | 13 ++++---- tests/CLI/modules/file_tests.py | 11 +++---- tests/managers/block_tests.py | 2 +- tests/managers/file_tests.py | 20 ++++++------ 7 files changed, 56 insertions(+), 56 deletions(-) diff --git a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py index cf79cd1a3..1a0c304d8 100644 --- a/SoftLayer/CLI/block/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/block/replication/disaster_recovery_failover.py @@ -1,36 +1,38 @@ -"""Failover an inaccessible file volume to its available replicant volume.""" +"""Failover an inaccessible block volume to its available replicant volume.""" # :license: MIT, see LICENSE for more details. import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via the API. To fail back to the original volume after using this method, open a support ticket. -To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""") +@click.command(epilog="""Failover an inaccessible block volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover.""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env def cli(env, volume_id, replicant_id): - """Failover an inaccessible file volume to its available replicant volume.""" + """Failover an inaccessible block volume to its available replicant volume.""" block_storage_manager = SoftLayer.BlockStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible block volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') block_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py index 3d175d909..a3f9373f7 100644 --- a/SoftLayer/CLI/file/replication/disaster_recovery_failover.py +++ b/SoftLayer/CLI/file/replication/disaster_recovery_failover.py @@ -4,14 +4,16 @@ import click import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -@click.command(epilog="""Failover an inaccessible block/file volume to its available replicant volume. -If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location. -This method does not allow for fail back via API. If you wish to test failover, please use SoftLayer_Network_Storage::failoverToReplicant. -After using this method, to fail back to the original volume, please open a support ticket""") +@click.command(epilog="""Failover an inaccessible file volume to its available replicant volume. +If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately +failover to an available replica in another location. This method does not allow for failback via API. +After using this method, to failback to the original volume, please open a support ticket. +If you wish to test failover, please use replica-failover. +""") @click.argument('volume-id') @click.option('--replicant-id', help="ID of the replicant volume") @environment.pass_env @@ -19,18 +21,19 @@ def cli(env, volume_id, replicant_id): """Failover an inaccessible file volume to its available replicant volume.""" file_storage_manager = SoftLayer.FileStorageManager(env.client) - click.secho("""WARNING : Failover an inaccessible block/file volume to its available replicant volume.""" - """If a volume (with replication) becomes inaccessible due to a disaster event, this method can be used to immediately failover to an available replica in another location.""" - """This method does not allow for fail back via the API.""" - """To fail back to the original volume after using this method, open a support ticket.""" - """To test failover, use SoftLayer_Network_Storage::failoverToReplicant instead.""",fg = 'red' ) - - if not (formatting.confirm('Are you sure you want to continue?')): + click.secho("""WARNING : Failover an inaccessible file volume to its available replicant volume.""" + """If a volume (with replication) becomes inaccessible due to a disaster event,""" + """this method can be used to immediately failover to an available replica in another location.""" + """This method does not allow for failback via the API.""" + """To failback to the original volume after using this method, open a support ticket.""" + """If you wish to test failover, use replica-failover instead.""", fg='red') + + if not formatting.confirm('Are you sure you want to continue?'): raise exceptions.CLIAbort('Aborted.') file_storage_manager.disaster_recovery_failover_to_replicant( volume_id, replicant_id ) - - click.echo("Disaster Recovery Failover to replicant is now in progress.") \ No newline at end of file + + click.echo("Disaster Recovery Failover to replicant is now in progress.") diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 89955569c..44e76c138 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -405,7 +405,7 @@ def disaster_recovery_failover_to_replicant(self, volume_id, replicant_id): :param integer replicant: ID of replicant to failover to :return: Returns whether failover to successful or not """ - return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) + return self.client.call('Network_Storage', 'disasterRecoveryFailoverToReplicant', replicant_id, id=volume_id) def failback_from_replicant(self, volume_id): """Failback from a volume replicant. @@ -413,7 +413,6 @@ def failback_from_replicant(self, volume_id): :param integer volume_id: The id of the volume :return: Returns whether failback was successful or not """ - return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id) def cancel_volume(self, volume_id, reason='No longer needed', immediate=False): diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 6ccebf66f..cbf1ba25f 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerAPIError from SoftLayer import testing -from SoftLayer.CLI import exceptions + import json import mock @@ -497,9 +498,8 @@ def test_replicant_failover(self): '--replicant-id=5678']) self.assert_no_fail(result) - self.assertEqual('Failover to replicant is now in progress.\n', - result.output) - + self.assertEqual('Failover to replicant is now in progress.\n', result.output) + @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.BlockStorageManager.disaster_recovery_failover_to_replicant') def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): @@ -509,8 +509,7 @@ def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confi '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) def test_replication_locations(self): result = self.run_command(['block', 'replica-locations', '1234']) @@ -579,7 +578,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['block', 'replica-failback', '12345678']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index b13596355..1bfe58e16 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -4,9 +4,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -from SoftLayer.CLI import exceptions import json import mock @@ -499,12 +499,10 @@ def test_replicant_failover_unsuccessful(self, failover_mock): def test_disaster_recovery_failover(self, disaster_recovery_failover_mock, confirm_mock): confirm_mock.return_value = True disaster_recovery_failover_mock.return_value = True - result = self.run_command(['file', 'disaster-recovery-failover', '12345678', - '--replicant-id=5678']) + result = self.run_command(['file', 'disaster-recovery-failover', '12345678', '--replicant-id=5678']) self.assert_no_fail(result) - self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', - result.output) + self.assertIn('Disaster Recovery Failover to replicant is now in progress.\n', result.output) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_disaster_recovery_failover_aborted(self, confirm_mock): @@ -514,8 +512,7 @@ def test_disaster_recovery_failover_aborted(self, confirm_mock): '--replicant-id=5678']) self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - + self.assertIsInstance(result.exception, exceptions.CLIAbort) def test_replicant_failback(self): result = self.run_command(['file', 'replica-failback', '12345678']) diff --git a/tests/managers/block_tests.py b/tests/managers/block_tests.py index 159553db7..8dc1cd83b 100644 --- a/tests/managers/block_tests.py +++ b/tests/managers/block_tests.py @@ -394,7 +394,7 @@ def test_disaster_recovery_failover(self): 'disasterRecoveryFailoverToReplicant', args=(5678,), identifier=1234, - ) + ) def test_replicant_failback(self): result = self.block.failback_from_replicant(1234) diff --git a/tests/managers/file_tests.py b/tests/managers/file_tests.py index 91f9325d5..11e35c001 100644 --- a/tests/managers/file_tests.py +++ b/tests/managers/file_tests.py @@ -290,16 +290,16 @@ def test_replicant_failover(self): ) def test_disaster_recovery_failover(self): - result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) - - self.assertEqual( - SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) - self.assert_called_with( - 'SoftLayer_Network_Storage', - 'disasterRecoveryFailoverToReplicant', - args=(5678,), - identifier=1234, - ) + result = self.file.disaster_recovery_failover_to_replicant(1234, 5678) + + self.assertEqual( + SoftLayer_Network_Storage.disasterRecoveryFailoverToReplicant, result) + self.assert_called_with( + 'SoftLayer_Network_Storage', + 'disasterRecoveryFailoverToReplicant', + args=(5678,), + identifier=1234, + ) def test_replicant_failback(self): result = self.file.failback_from_replicant(1234) From 5ad1d04bc5f4afe2618c709ce1cb067940d4dc8a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Mon, 1 Feb 2021 16:04:43 -0400 Subject: [PATCH 0529/1582] Add pagination to object storage list accounts. --- SoftLayer/managers/object_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 2560d26c8..731d48d13 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -30,7 +30,7 @@ def __init__(self, client): def list_accounts(self): """Lists your object storage accounts.""" return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK) + mask=LIST_ACCOUNTS_MASK, iter=True, limit=10) def list_endpoints(self): """Lists the known object storage endpoints.""" From 09f3ce5f99a89e3093b4e627c4eac6e8a9267dfc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 3 Feb 2021 15:53:21 -0400 Subject: [PATCH 0530/1582] Refactor object storage list accounts. --- SoftLayer/CLI/object_storage/list_accounts.py | 8 ++++++-- SoftLayer/managers/object_storage.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/object_storage/list_accounts.py b/SoftLayer/CLI/object_storage/list_accounts.py index c49aecc67..e7862711c 100644 --- a/SoftLayer/CLI/object_storage/list_accounts.py +++ b/SoftLayer/CLI/object_storage/list_accounts.py @@ -9,12 +9,16 @@ @click.command() +@click.option('--limit', + type=int, + default=10, + help="Result limit") @environment.pass_env -def cli(env): +def cli(env, limit): """List object storage accounts.""" mgr = SoftLayer.ObjectStorageManager(env.client) - accounts = mgr.list_accounts() + accounts = mgr.list_accounts(limit=limit) table = formatting.Table(['id', 'name', 'apiType']) table.sortby = 'id' api_type = None diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index 731d48d13..f9d37440e 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -27,10 +27,10 @@ class ObjectStorageManager(object): def __init__(self, client): self.client = client - def list_accounts(self): + def list_accounts(self, limit=10): """Lists your object storage accounts.""" return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK, iter=True, limit=10) + mask=LIST_ACCOUNTS_MASK, iter=True, limit=limit) def list_endpoints(self): """Lists the known object storage endpoints.""" From 461cee479e078ed7b10094ee6758b438944a6188 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 4 Feb 2021 18:28:11 -0400 Subject: [PATCH 0531/1582] Add slcli vs create by router data. --- SoftLayer/CLI/virt/create.py | 6 ++++ SoftLayer/managers/vs.py | 39 +++++++++++++++++++-- tests/CLI/modules/vs/vs_create_tests.py | 43 +++++++++++++++++++++++ tests/managers/vs/vs_tests.py | 45 +++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index d9864a890..23a18457d 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -95,6 +95,8 @@ def _parse_create_args(client, args): "private_vlan": args.get('vlan_private', None), "public_subnet": args.get('subnet_public', None), "private_subnet": args.get('subnet_private', None), + "public_router": args.get('router_public', None), + "private_router": args.get('router_private', None), } # The primary disk is included in the flavor and the local_disk flag is not needed @@ -192,6 +194,10 @@ def _parse_create_args(client, args): help="The ID of the public SUBNET on which you want the virtual server placed") @click.option('--subnet-private', type=click.INT, help="The ID of the private SUBNET on which you want the virtual server placed") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--public-security-group', '-S', help=('Security group ID to associate with the public interface')) @helpers.multi_option('--private-security-group', '-s', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 1a03db1c6..3c3eab3a2 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,6 +470,7 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, + public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -533,6 +534,15 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} + if private_router or public_router: + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + private_router, public_router) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) @@ -581,7 +591,8 @@ def _generate_create_dict( def _create_network_components( self, public_vlan=None, private_vlan=None, - private_subnet=None, public_subnet=None): + private_subnet=None, public_subnet=None, + private_router=None, public_router=None): parameters = {} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} @@ -598,6 +609,12 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} + return parameters @retry(logger=LOGGER) @@ -685,7 +702,21 @@ def verify_create_instance(self, **kwargs): kwargs.pop('tags', None) create_options = self._generate_create_dict(**kwargs) template = self.guest.generateOrderTemplate(create_options) - if 'private_subnet' in kwargs or 'public_subnet' in kwargs: + if kwargs.get('public_router') or kwargs.get('private_router'): + if kwargs.get('private_vlan') or kwargs.get('public_vlan') or kwargs.get('private_subnet') \ + or kwargs.get('public_subnet'): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + vsi = template['virtualGuests'][0] + network_components = self._create_network_components(kwargs.get('public_vlan', None), + kwargs.get('private_vlan', None), + kwargs.get('private_subnet', None), + kwargs.get('public_subnet', None), + kwargs.get('private_router', None), + kwargs.get('public_router', None)) + vsi.update(network_components) + + if kwargs.get('private_subnet') or kwargs.get('public_subnet'): vsi = template['virtualGuests'][0] network_components = self._create_network_components(kwargs.get('public_vlan', None), kwargs.get('private_vlan', None), @@ -693,6 +724,9 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) + print("template") + print(template) + return template def create_instance(self, **kwargs): @@ -1121,6 +1155,7 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') + print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 53c3bdc97..2ad0f8647 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -93,6 +93,49 @@ def test_create_vlan_subnet(self, confirm_mock): self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_create_by_router(self, confirm_mock): + confirm_mock.return_value = True + + result = self.run_command(['vs', 'create', + '--cpu=2', + '--domain=example.com', + '--hostname=host', + '--os=UBUNTU_LATEST', + '--memory=1', + '--billing=hourly', + '--datacenter=dal05', + '--router-private=577940', + '--router-public=1639255', + '--tag=dev', + '--tag=green']) + + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + args = ({ + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { + 'router': { + 'id': 577940 + } + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) + + self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_with_wait_ready(self, confirm_mock): mock = self.set_mock('SoftLayer_Virtual_Guest', 'getObject') diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index e858b2577..db8bb4ed8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -557,6 +557,38 @@ def test_generate_private_vlan(self): self.assertEqual(data, assert_data) + def test_generate_by_router_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + + def test_generate_by_router_and_subnet(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._generate_create_dict, + cpus=1, + memory=1, + hostname='test', + domain='example.com', + os_code="STRING", + private_router=1, + private_subnet=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_generate_sec_group(self): data = self.vs._generate_create_dict( cpus=1, @@ -596,6 +628,19 @@ def test_create_network_components_vlan_subnet_private_vlan_subnet_public(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers(self): + data = self.vs._create_network_components( + private_router=1, + public_router=1 + ) + + assert_data = { + 'primaryBackendNetworkComponent': {'router': {'id': 1}}, + 'primaryNetworkComponent': {'router': {'id': 1}}, + } + + self.assertEqual(data, assert_data) + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From 485b231d95f00c6251bf779086044e8cf136c963 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:58:49 -0400 Subject: [PATCH 0532/1582] #1410 fix conflicts after updating from origin --- SoftLayer/managers/object_storage.py | 32 ++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/object_storage.py b/SoftLayer/managers/object_storage.py index e27b8975f..5efadeabc 100644 --- a/SoftLayer/managers/object_storage.py +++ b/SoftLayer/managers/object_storage.py @@ -6,6 +6,9 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.exceptions import SoftLayerError +from SoftLayer import utils + LIST_ACCOUNTS_MASK = '''mask[ id,username,notes,vendorName,serviceResource ]''' @@ -15,7 +18,7 @@ ]''' -class ObjectStorageManager(object): +class ObjectStorageManager(utils.IdentifierMixin, object): """Manager for SoftLayer Object Storage accounts. See product information here: https://www.ibm.com/cloud/object-storage @@ -26,11 +29,16 @@ class ObjectStorageManager(object): def __init__(self, client): self.client = client + self.resolvers = [self._get_id_from_username] - def list_accounts(self, limit=10): + def list_accounts(self, object_mask=None, object_filter=None, limit=10): """Lists your object storage accounts.""" - return self.client.call('Account', 'getHubNetworkStorage', - mask=LIST_ACCOUNTS_MASK, iter=True, limit=limit) + object_mask = object_mask if object_mask else LIST_ACCOUNTS_MASK + return self.client.call('Account', + 'getHubNetworkStorage', + mask=object_mask, + filter=object_filter, + limit=limit) def list_endpoints(self): """Lists the known object storage endpoints.""" @@ -96,3 +104,19 @@ def list_credential(self, identifier): return self.client.call('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials', id=identifier) + + def _get_id_from_username(self, username): + """Looks up a username's id + + :param string username: Username to lookup + :returns: The id that matches username. + """ + _mask = "mask[id,username]" + _filter = {'hubNetworkStorage': {'username': utils.query_filter(username)}} + account = self.list_accounts(_mask, _filter) + if len(account) == 1: + return [account[0]['id']] + elif len(account) > 1: + raise SoftLayerError("Multiple object storage accounts found with the name: {}".format(username)) + else: + raise SoftLayerError("Unable to find object storage account id for: {}".format(username)) From b0f7933e1c32cd09a952ce66eeea0aa19f2d0cbe Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:11:56 -0400 Subject: [PATCH 0533/1582] #1410 add resolve id test to object storage --- tests/managers/object_storage_tests.py | 52 ++++++-------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index e5042080d..73558a29b 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -79,43 +79,15 @@ def test_limit_credential(self): self.assertEqual(credential, 2) def test_list_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') - accounts.return_value = [ - { - "id": 1103123, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", - "username": "XfHhBNBPlPdlWyaP3fsd", - "type": { - "name": "S3 Compatible Signature" - } - }, - { - "id": 1102341, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", - "username": "XfHhBNBPlPdlWyaP", - "type": { - "name": "S3 Compatible Signature" - } - } - ] - credential = self.object_storage.list_credential(100) - self.assertEqual(credential, - [ - { - "id": 1103123, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXsf4sf", - "username": "XfHhBNBPlPdlWyaP3fsd", - "type": { - "name": "S3 Compatible Signature" - } - }, - { - "id": 1102341, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCyXMkAF", - "username": "XfHhBNBPlPdlWyaP", - "type": { - "name": "S3 Compatible Signature" - } - } - ] - ) + credentials = self.object_storage.list_credential(100) + self.assertIsInstance(credentials, list) + self.assert_called_with('SoftLayer_Network_Storage_Hub_Cleversafe_Account', + 'getCredentials', + identifier=100) + + def test_resolve_ids(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [{'id': 12345, 'username': 'test'}] + identifier = self.object_storage.resolve_ids('test') + self.assertEqual(identifier, [12345]) + self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') From 35f7a0ca64e3012c4f204c1cac1536ef42b57f02 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:20:46 -0400 Subject: [PATCH 0534/1582] #1410 add username lookup to slcli object-storage credential list --- SoftLayer/CLI/object_storage/credential/list.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/list.py b/SoftLayer/CLI/object_storage/credential/list.py index 647e4224c..53f878fce 100644 --- a/SoftLayer/CLI/object_storage/credential/list.py +++ b/SoftLayer/CLI/object_storage/credential/list.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,15 +16,17 @@ def cli(env, identifier): """Retrieve credentials used for generating an AWS signature. Max of 2.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential_list = mgr.list_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential_list = mgr.list_credential(storage_id) + table = formatting.Table(['id', 'password', 'username', 'type_name']) for credential in credential_list: table.add_row([ - credential['id'], - credential['password'], - credential['username'], - credential['type']['name'] + credential.get('id'), + credential.get('password'), + credential.get('username'), + credential.get('type', {}).get('name') ]) env.fout(table) From d833f77452a0261622f93492cd6ec6bfae2c6a40 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 4 Feb 2021 17:22:22 -0400 Subject: [PATCH 0535/1582] #1410 add username lookup test to slcli object-storage credential list --- tests/CLI/modules/object_storage_tests.py | 25 ++++++----------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 74d70152e..ee5218fa4 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +from unittest import mock from SoftLayer import testing @@ -82,25 +83,11 @@ def test_limit_credential(self): self.assertEqual(json.loads(result.output), [{'limit': 2}]) def test_list_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentials') - accounts.return_value = [{'id': 1103123, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', - 'type': {'name': 'S3 Compatible Signature'}, - 'username': 'XfHhBNBPlPdlWya'}, - {'id': 1103333, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9', - 'type': {'name': 'S3 Compatible Signature'}, - 'username': 'XfHhBNBPlPd'}] - result = self.run_command(['object-storage', 'credential', 'list', '100']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_list_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'list', 'test']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'id': 1103123, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCyXM', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPdlWya'}, - {'id': 1103333, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPd'}]) From 8b438469494fef39fe413ad4fb8bc39d9caaf25b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:28:37 -0400 Subject: [PATCH 0536/1582] #1410 add resolve id tests to object storage --- tests/managers/object_storage_tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/managers/object_storage_tests.py b/tests/managers/object_storage_tests.py index 73558a29b..16081cc83 100644 --- a/tests/managers/object_storage_tests.py +++ b/tests/managers/object_storage_tests.py @@ -6,6 +6,7 @@ """ import SoftLayer from SoftLayer import fixtures +from SoftLayer import SoftLayerError from SoftLayer import testing @@ -91,3 +92,14 @@ def test_resolve_ids(self): identifier = self.object_storage.resolve_ids('test') self.assertEqual(identifier, [12345]) self.assert_called_with('SoftLayer_Account', 'getHubNetworkStorage') + + def test_resolve_ids_fail_multiple(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [{'id': 12345, 'username': 'test'}, + {'id': 12345, 'username': 'test'}] + self.assertRaises(SoftLayerError, self.object_storage.resolve_ids, 'test') + + def test_resolve_ids_fail_no_found(self): + accounts = self.set_mock('SoftLayer_Account', 'getHubNetworkStorage') + accounts.return_value = [] + self.assertRaises(SoftLayerError, self.object_storage.resolve_ids, 'test') From e51759d6d4d08bafc44d9ff2428133ee64d6b827 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 5 Feb 2021 11:43:24 -0400 Subject: [PATCH 0537/1582] #1410 add username lookup to slcli object-storage credential limit, delete, create --- .../CLI/object_storage/credential/create.py | 12 +++-- .../CLI/object_storage/credential/delete.py | 4 +- .../CLI/object_storage/credential/limit.py | 4 +- ..._Network_Storage_Hub_Cleversafe_Account.py | 4 ++ tests/CLI/modules/object_storage_tests.py | 46 ++++++++----------- 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/SoftLayer/CLI/object_storage/credential/create.py b/SoftLayer/CLI/object_storage/credential/create.py index 934ac7651..a7ce36755 100644 --- a/SoftLayer/CLI/object_storage/credential/create.py +++ b/SoftLayer/CLI/object_storage/credential/create.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,14 +16,15 @@ def cli(env, identifier): """Create credentials for an IBM Cloud Object Storage Account""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential = mgr.create_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential = mgr.create_credential(storage_id) table = formatting.Table(['id', 'password', 'username', 'type_name']) table.sortby = 'id' table.add_row([ - credential['id'], - credential['password'], - credential['username'], - credential['type']['name'] + credential.get('id'), + credential.get('password'), + credential.get('username'), + credential.get('type', {}).get('name') ]) env.fout(table) diff --git a/SoftLayer/CLI/object_storage/credential/delete.py b/SoftLayer/CLI/object_storage/credential/delete.py index 7b066ba59..da1ddaeb9 100644 --- a/SoftLayer/CLI/object_storage/credential/delete.py +++ b/SoftLayer/CLI/object_storage/credential/delete.py @@ -5,6 +5,7 @@ import SoftLayer from SoftLayer.CLI import environment +from SoftLayer.CLI import helpers @click.command() @@ -16,6 +17,7 @@ def cli(env, identifier, credential_id): """Delete the credential of an Object Storage Account.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential = mgr.delete_credential(identifier, credential_id=credential_id) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential = mgr.delete_credential(storage_id, credential_id=credential_id) env.fout(credential) diff --git a/SoftLayer/CLI/object_storage/credential/limit.py b/SoftLayer/CLI/object_storage/credential/limit.py index cc3ad115c..689a8cef4 100644 --- a/SoftLayer/CLI/object_storage/credential/limit.py +++ b/SoftLayer/CLI/object_storage/credential/limit.py @@ -6,6 +6,7 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() @@ -15,7 +16,8 @@ def cli(env, identifier): """Credential limits for this IBM Cloud Object Storage account.""" mgr = SoftLayer.ObjectStorageManager(env.client) - credential_limit = mgr.limit_credential(identifier) + storage_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Object Storage') + credential_limit = mgr.limit_credential(storage_id) table = formatting.Table(['limit']) table.add_row([ credential_limit, diff --git a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py index 4d066cf7e..ab72576e4 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Storage_Hub_Cleversafe_Account.py @@ -46,3 +46,7 @@ "storageLocation": "us-standard" } ] + +getCredentialLimit = 2 + +credentialDelete = True diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index ee5218fa4..2e843906d 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -40,47 +40,37 @@ def test_list_endpoints(self): 'public': 'https://dal05/auth/v1.0/'}]) def test_create_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialCreate') - accounts.return_value = { - "accountId": "12345", - "createDate": "2019-04-05T13:25:25-06:00", - "id": 11111, - "password": "nwUEUsx6PiEoN0B1Xe9z9hUCy", - "username": "XfHhBNBPlPdl", - "type": { - "description": "A credential for generating S3 Compatible Signatures.", - "keyName": "S3_COMPATIBLE_SIGNATURE", - "name": "S3 Compatible Signature" - } - } - result = self.run_command(['object-storage', 'credential', 'create', '100']) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_create_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'create', 'test']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), - [{'id': 11111, - 'password': 'nwUEUsx6PiEoN0B1Xe9z9hUCy', - 'type_name': 'S3 Compatible Signature', - 'username': 'XfHhBNBPlPdl'}] - ) def test_delete_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'credentialDelete') - accounts.return_value = True - result = self.run_command(['object-storage', 'credential', 'delete', '-c', 100, '100']) - self.assert_no_fail(result) self.assertEqual(result.output, 'True\n') - def test_limit_credential(self): - accounts = self.set_mock('SoftLayer_Network_Storage_Hub_Cleversafe_Account', 'getCredentialLimit') - accounts.return_value = 2 + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_delete_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'delete', 'test']) + self.assert_no_fail(result) + def test_limit_credential(self): result = self.run_command(['object-storage', 'credential', 'limit', '100']) self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), [{'limit': 2}]) + self.assertIn('limit', result.output) + + @mock.patch('SoftLayer.CLI.helpers.resolve_id') + def test_limit_credential_by_username(self, resolve_id_mock): + resolve_id_mock.return_value = 100 + result = self.run_command(['object-storage', 'credential', 'limit', 'test']) + self.assert_no_fail(result) def test_list_credential(self): result = self.run_command(['object-storage', 'credential', 'list', '100']) From b66b563e0dfb14bebb1ab4497493babd507e9fb4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 14:47:37 -0400 Subject: [PATCH 0538/1582] Fix tox analysis. --- SoftLayer/managers/vs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 3c3eab3a2..20068ef61 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -470,7 +470,6 @@ def _generate_create_dict( datacenter=None, os_code=None, image_id=None, dedicated=False, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, - public_router=None, private_router=None, userdata=None, nic_speed=None, disks=None, post_uri=None, private=False, ssh_keys=None, public_security_groups=None, private_security_groups=None, boot_mode=None, transient=False, **kwargs): @@ -534,13 +533,14 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if private_router or public_router: + if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " "only router, not all options") network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet, - private_router, public_router) + kwargs.get('private_router'), + kwargs.get('public_router')) data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: From 9a0f48a264a293df2233eae4e88878954d8de432 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:04:05 -0400 Subject: [PATCH 0539/1582] Fix tox analysis. --- SoftLayer/managers/vs.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 20068ef61..6de17d0dd 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,20 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -589,6 +576,21 @@ def _generate_create_dict( return data + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + if kwargs.get('private_router') or kwargs.get('public_router'): + if private_vlan or public_vlan or private_subnet or public_subnet: + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet) + data.update(network_components) + def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, @@ -724,9 +726,6 @@ def verify_create_instance(self, **kwargs): kwargs.get('public_subnet', None)) vsi.update(network_components) - print("template") - print(template) - return template def create_instance(self, **kwargs): @@ -1155,7 +1154,6 @@ def order_guest(self, guest_object, test=False): if guest_object.get('placement_id'): template['virtualGuests'][0]['placementGroupId'] = guest_object.get('placement_id') - print(template) if test: result = self.client.call('Product_Order', 'verifyOrder', template) else: From fde0cf6c0566f5e4f5a3e364bf53c257c58f3cbc Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 15:37:51 -0400 Subject: [PATCH 0540/1582] Add method docstring. --- SoftLayer/managers/vs.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6de17d0dd..01c2b05e5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,10 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, + public_vlan) + + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,7 +579,16 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + """Get the network components structure. + + :param kwargs: Vs item list. + :param int private_subnet: Private subnet id. + :param int private_vlan: Private vlan id. + :param int public_subnet: Public subnet id. + :param int public_vlan: Public vlan id. + """ + network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -585,11 +597,12 @@ def get_network_components(self, data, kwargs, private_subnet, private_vlan, pub private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - data.update(network_components) + if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - data.update(network_components) + + return network_components def _create_network_components( self, public_vlan=None, private_vlan=None, From 6d354ce3de711ca0bab1443bcb87662220caeb21 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Fri, 5 Feb 2021 16:09:10 -0400 Subject: [PATCH 0541/1582] Fix tox test issues. --- SoftLayer/managers/vs.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 01c2b05e5..0994530b3 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,10 +533,7 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - network_components = self.get_network_components(kwargs, private_subnet, private_vlan, public_subnet, - public_vlan) - - data.update(network_components) + self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -579,16 +576,16 @@ def _generate_create_dict( return data - def get_network_components(self, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): + def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): """Get the network components structure. + :param data: Array variable to add the network structure. :param kwargs: Vs item list. :param int private_subnet: Private subnet id. :param int private_vlan: Private vlan id. :param int public_subnet: Public subnet id. :param int public_vlan: Public vlan id. """ - network_components = None if kwargs.get('private_router') or kwargs.get('public_router'): if private_vlan or public_vlan or private_subnet or public_subnet: raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " @@ -597,12 +594,11 @@ def get_network_components(self, kwargs, private_subnet, private_vlan, public_su private_subnet, public_subnet, kwargs.get('private_router'), kwargs.get('public_router')) - + data.update(network_components) if private_vlan or public_vlan or private_subnet or public_subnet: network_components = self._create_network_components(public_vlan, private_vlan, private_subnet, public_subnet) - - return network_components + data.update(network_components) def _create_network_components( self, public_vlan=None, private_vlan=None, From b2f1af63973a4e79995337b020369cbfd6770aaf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Tue, 9 Feb 2021 14:23:14 -0400 Subject: [PATCH 0542/1582] Refactor network components method. --- SoftLayer/managers/vs.py | 44 +++++++++++------------------------ tests/managers/vs/vs_tests.py | 12 ++++++++++ 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 0994530b3..b247e8be5 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -533,7 +533,11 @@ def _generate_create_dict( if datacenter: data["datacenter"] = {"name": datacenter} - self.get_network_components(data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan) + network_components = self._create_network_components(public_vlan, private_vlan, + private_subnet, public_subnet, + kwargs.get('private_router'), + kwargs.get('public_router')) + data.update(network_components) if public_security_groups: secgroups = [{'securityGroup': {'id': int(sg)}} @@ -576,35 +580,19 @@ def _generate_create_dict( return data - def get_network_components(self, data, kwargs, private_subnet, private_vlan, public_subnet, public_vlan): - """Get the network components structure. - - :param data: Array variable to add the network structure. - :param kwargs: Vs item list. - :param int private_subnet: Private subnet id. - :param int private_vlan: Private vlan id. - :param int public_subnet: Public subnet id. - :param int public_vlan: Public vlan id. - """ - if kwargs.get('private_router') or kwargs.get('public_router'): - if private_vlan or public_vlan or private_subnet or public_subnet: - raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " - "only router, not all options") - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet, - kwargs.get('private_router'), - kwargs.get('public_router')) - data.update(network_components) - if private_vlan or public_vlan or private_subnet or public_subnet: - network_components = self._create_network_components(public_vlan, private_vlan, - private_subnet, public_subnet) - data.update(network_components) - def _create_network_components( self, public_vlan=None, private_vlan=None, private_subnet=None, public_subnet=None, private_router=None, public_router=None): parameters = {} + if any([private_router, public_router]) and any([private_vlan, public_vlan, private_subnet, public_subnet]): + raise exceptions.SoftLayerError("You have to select network vlan or network vlan with a subnet or " + "only router, not all options") + + if private_router: + parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if private_vlan: parameters['primaryBackendNetworkComponent'] = {"networkVlan": {"id": int(private_vlan)}} if public_vlan: @@ -620,12 +608,6 @@ def _create_network_components( parameters['primaryBackendNetworkComponent']['networkVlan']['primarySubnet'] = {'id': int(private_subnet)} - if private_router: - parameters['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} - - if public_router: - parameters['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} - return parameters @retry(logger=LOGGER) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index db8bb4ed8..c75e5e3b9 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -641,6 +641,18 @@ def test_create_network_components_by_routers(self): self.assertEqual(data, assert_data) + def test_create_network_components_by_routers_and_vlan(self): + actual = self.assertRaises( + exceptions.SoftLayerError, + self.vs._create_network_components, + private_router=1, + public_router=1, + private_vlan=1 + ) + + self.assertEqual(str(actual), "You have to select network vlan or network vlan with a subnet or only router, " + "not all options") + def test_create_network_components_vlan_subnet_private(self): data = self.vs._create_network_components( private_vlan=1, From fc1f67ea245ce08ebfbb9bffd9293271ea07841f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:02:25 -0400 Subject: [PATCH 0543/1582] Add IOPs data to block volume list. --- SoftLayer/CLI/block/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 44489f928..769aad233 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -23,7 +23,7 @@ mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), column_helper.Column('bytes_used', ('bytesUsed',), mask="bytesUsed"), - column_helper.Column('iops', ('iops',), mask="iops"), + column_helper.Column('IOPs', ('provisionedIops',), mask="provisionedIops"), column_helper.Column('ip_addr', ('serviceResourceBackendIpAddress',), mask="serviceResourceBackendIpAddress"), column_helper.Column('lunId', ('lunId',), mask="lunId"), @@ -44,7 +44,7 @@ 'storage_type', 'capacity_gb', 'bytes_used', - 'iops', + 'IOPs', 'ip_addr', 'lunId', 'active_transactions', From e30066a08419ee9c9f38a4bdaaad2422771189eb Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Thu, 11 Feb 2021 15:09:23 -0400 Subject: [PATCH 0544/1582] Add IOPs data to block volume-list unit test. --- tests/CLI/modules/block_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index f061d36a2..97feb58be 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -130,7 +130,7 @@ def test_volume_list(self): 'capacity_gb': 20, 'datacenter': 'dal05', 'id': 100, - 'iops': None, + 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, 'notes': "{'status': 'availabl", From 2fae9ff48be806122ae79d85f62182582d622765 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:18:48 -0400 Subject: [PATCH 0545/1582] add a flags in the report bandwidth --- SoftLayer/CLI/report/bandwidth.py | 66 +++++--- tests/CLI/modules/report_tests.py | 250 ++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 23d1a157c..bd651c90a 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -176,7 +176,7 @@ def _get_virtual_bandwidth(env, start, end): '--start', callback=_validate_datetime, default=( - datetime.datetime.now() - datetime.timedelta(days=30) + datetime.datetime.now() - datetime.timedelta(days=30) ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( @@ -187,8 +187,12 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) +@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', + default=False) +@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', + default=False) @environment.pass_env -def cli(env, start, end, sortby): +def cli(env, start, end, sortby, virtual, server): """Bandwidth report for every pool/server. This reports on the total data transfered for each virtual sever, hardware @@ -213,24 +217,46 @@ def f_type(key, results): return (result['counter'] for result in results if result['type'] == key) - try: + def _input_to_table(item): + "Input metric data to table" + pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) + pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) + pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) + pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) + table.add_row([ + item['type'], + item['name'], + formatting.b_to_gb(pub_in), + formatting.b_to_gb(pub_out), + formatting.b_to_gb(pri_in), + formatting.b_to_gb(pri_out), + item.get('pool') or formatting.blank(), + ]) + + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing virtual collected results and then aborting.") + + elif server: + try: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing server collected results and then aborting.") + else: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end)): - pub_in = int(sum(f_type('publicIn_net_octet', item['data']))) - pub_out = int(sum(f_type('publicOut_net_octet', item['data']))) - pri_in = int(sum(f_type('privateIn_net_octet', item['data']))) - pri_out = int(sum(f_type('privateOut_net_octet', item['data']))) - table.add_row([ - item['type'], - item['name'], - formatting.b_to_gb(pub_in), - formatting.b_to_gb(pub_out), - formatting.b_to_gb(pri_in), - formatting.b_to_gb(pri_out), - item.get('pool') or formatting.blank(), - ]) - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + try: + pass + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 3d580edad..896e61e20 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -194,3 +194,253 @@ def test_bandwidth_report(self): 300, ) self.assertEqual(expected_args, call.args) + + def test_virtual_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + guests = self.set_mock('SoftLayer_Account', 'getVirtualGuests') + guests.return_value = [{ + 'id': 201, + 'metricTrackingObjectId': 201, + 'hostname': 'host1', + }, { + 'id': 202, + 'hostname': 'host2', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 203, + 'metricTrackingObjectId': 203, + 'hostname': 'host3', + 'virtualRack': {'id': 2, 'bandwidthAllotmentTypeId': 2}, + }] + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--virtual', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }, { + 'hostname': 'host3', + 'pool': 2, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'virtual', + }], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=1) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=3) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=201) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=203) + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) + + def test_server_bandwidth_report(self): + racks = self.set_mock('SoftLayer_Account', 'getVirtualDedicatedRacks') + racks.return_value = [{ + 'id': 1, + 'name': 'pool1', + 'metricTrackingObjectId': 1, + }, { + 'id': 2, + 'name': 'pool2', + }, { + 'id': 3, + 'name': 'pool3', + 'metricTrackingObjectId': 3, + }] + hardware = self.set_mock('SoftLayer_Account', 'getHardware') + hardware.return_value = [{ + 'id': 101, + 'metricTrackingObject': {'id': 101}, + 'hostname': 'host1', + }, { + 'id': 102, + 'hostname': 'host2', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }, { + 'id': 103, + 'metricTrackingObject': {'id': 103}, + 'hostname': 'host3', + 'virtualRack': {'id': 1, 'bandwidthAllotmentTypeId': 2}, + }] + + summary_data = self.set_mock('SoftLayer_Metric_Tracking_Object', + 'getSummaryData') + summary_data.return_value = [ + {'type': 'publicIn_net_octet', 'counter': 10}, + {'type': 'publicOut_net_octet', 'counter': 20}, + {'type': 'privateIn_net_octet', 'counter': 30}, + {'type': 'privateOut_net_octet', 'counter': 40}, + ] + + result = self.run_command([ + 'report', + 'bandwidth', + '--start=2016-02-04', + '--end=2016-03-04 12:34:56', + '--server', + ]) + + self.assert_no_fail(result) + stripped_output = '[' + result.output.split('[', 1)[1] + self.assertEqual([ + { + 'hostname': 'pool1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'pool3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'pool', + }, { + 'hostname': 'host1', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, { + 'hostname': 'host3', + 'pool': None, + 'private_in': 30, + 'private_out': 40, + 'public_in': 10, + 'public_out': 20, + 'type': 'hardware', + }, ], + json.loads(stripped_output), + ) + self.assertEqual( + 4, + len(self.calls('SoftLayer_Metric_Tracking_Object', + 'getSummaryData')), + ) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=101) + self.assert_called_with('SoftLayer_Metric_Tracking_Object', + 'getSummaryData', + identifier=103) + + call = self.calls('SoftLayer_Metric_Tracking_Object', 'getSummaryData', + identifier=1)[0] + expected_args = ( + '2016-02-04 00:00:00 ', + '2016-03-04 12:34:56 ', + [{ + 'keyName': 'PUBLICIN', + 'name': 'publicIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PUBLICOUT', + 'name': 'publicOut', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEIN', + 'name': 'privateIn', + 'summaryType': 'sum', + }, { + 'keyName': 'PRIVATEOUT', + 'name': 'privateOut', + 'summaryType': 'sum', + }], + 300, + ) + self.assertEqual(expected_args, call.args) From ecb46a514f1c295ff6595bdd7edc5e3650dc0e92 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 12 Feb 2021 18:40:33 -0400 Subject: [PATCH 0546/1582] fix the tox tool --- tests/CLI/modules/report_tests.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/CLI/modules/report_tests.py b/tests/CLI/modules/report_tests.py index 896e61e20..8489aeeab 100644 --- a/tests/CLI/modules/report_tests.py +++ b/tests/CLI/modules/report_tests.py @@ -102,7 +102,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'pool3', 'pool': None, @@ -110,7 +110,7 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'pool', + 'type': 'pool' }, { 'hostname': 'host1', 'pool': None, @@ -118,15 +118,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host3', - 'pool': 2, + 'pool': None, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'virtual', + 'type': 'hardware' }, { 'hostname': 'host1', 'pool': None, @@ -134,16 +134,15 @@ def test_bandwidth_report(self): 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', + 'type': 'virtual' }, { 'hostname': 'host3', - 'pool': None, + 'pool': 2, 'private_in': 30, 'private_out': 40, 'public_in': 10, 'public_out': 20, - 'type': 'hardware', - }], + 'type': 'virtual'}], json.loads(stripped_output), ) self.assertEqual( From ea5c5c9f31c1b6be116a6c1ae10c35414b2c5ee9 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 12 Feb 2021 20:29:26 -0400 Subject: [PATCH 0547/1582] #1400 add 2FA and classic APIKeys fields to user list as default values --- SoftLayer/CLI/user/list.py | 19 ++++++++++++++++--- SoftLayer/fixtures/SoftLayer_Account.py | 8 ++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/user/list.py b/SoftLayer/CLI/user/list.py index 142f824ab..10ba388a2 100644 --- a/SoftLayer/CLI/user/list.py +++ b/SoftLayer/CLI/user/list.py @@ -8,6 +8,8 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +TWO_FACTO_AUTH = 'externalBindingCount' +CLASSIC_API_KEYS = 'apiAuthenticationKeyCount' COLUMNS = [ column_helper.Column('id', ('id',)), @@ -17,15 +19,17 @@ column_helper.Column('status', ('userStatus', 'name')), column_helper.Column('hardwareCount', ('hardwareCount',)), column_helper.Column('virtualGuestCount', ('virtualGuestCount',)), - column_helper.Column('2FAs', ('externalBindingCount',)), - column_helper.Column('classicAPIKeys', ('apiAuthenticationKeyCount',)) + column_helper.Column('2FA', (TWO_FACTO_AUTH,)), + column_helper.Column('classicAPIKey', (CLASSIC_API_KEYS,)) ] DEFAULT_COLUMNS = [ 'id', 'username', 'email', - 'displayName' + 'displayName', + '2FA', + 'classicAPIKey', ] @@ -44,7 +48,16 @@ def cli(env, columns): table = formatting.Table(columns.columns) for user in users: + user = _yes_format(user, [TWO_FACTO_AUTH, CLASSIC_API_KEYS]) table.add_row([value or formatting.blank() for value in columns.row(user)]) env.fout(table) + + +def _yes_format(user, keys): + """Changes all dictionary values to yes whose keys are in the list. """ + for key in keys: + if user.get(key): + user[key] = 'yes' + return user diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index a1c667cf1..123e0bc47 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -691,13 +691,17 @@ 'id': 11100, 'userStatus': {'name': 'Active'}, 'username': 'SL1234', - 'virtualGuestCount': 99}, + 'virtualGuestCount': 99, + 'externalBindingCount': 1, + 'apiAuthenticationKeyCount': 1, + }, {'displayName': 'PulseL', 'hardwareCount': 100, 'id': 11111, 'userStatus': {'name': 'Active'}, 'username': 'sl1234-abob', - 'virtualGuestCount': 99} + 'virtualGuestCount': 99, + } ] getReservedCapacityGroups = [ From ac7465fe94e35dee8f256f2aa97eb08669336fd5 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 15:35:12 -0400 Subject: [PATCH 0548/1582] Add the option network component by router to slcli hw create. --- SoftLayer/CLI/hardware/create.py | 8 ++++++- SoftLayer/managers/hardware.py | 8 ++++++- tests/CLI/modules/server_tests.py | 20 ++++++++++++++++ tests/managers/hardware_tests.py | 38 +++++++++++-------------------- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/SoftLayer/CLI/hardware/create.py b/SoftLayer/CLI/hardware/create.py index 40fa871bc..a1d373e14 100644 --- a/SoftLayer/CLI/hardware/create.py +++ b/SoftLayer/CLI/hardware/create.py @@ -31,6 +31,10 @@ help="Exports options to a template file") @click.option('--wait', type=click.INT, help="Wait until the server is finished provisioning for up to X seconds before returning") +@click.option('--router-public', type=click.INT, + help="The ID of the public ROUTER on which you want the virtual server placed") +@click.option('--router-private', type=click.INT, + help="The ID of the private ROUTER on which you want the virtual server placed") @helpers.multi_option('--key', '-k', help="SSH keys to add to the root user") @helpers.multi_option('--extra', '-e', help="Extra option Key Names") @environment.pass_env @@ -57,7 +61,9 @@ def cli(env, **args): 'port_speed': args.get('port_speed'), 'no_public': args.get('no_public') or False, 'extras': args.get('extra'), - 'network': args.get('network') + 'network': args.get('network'), + 'public_router': args.get('router_public', None), + 'private_router': args.get('router_private', None) } # Do not create hardware server with --test or --export diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..ecf1a89c2 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -492,7 +492,9 @@ def _generate_create_dict(self, hourly=True, no_public=False, extras=None, - network=None): + network=None, + public_router=None, + private_router=None): """Translates arguments into a dictionary for creating a server.""" extras = extras or [] @@ -535,6 +537,10 @@ def _generate_create_dict(self, 'domain': domain, }] } + if private_router: + extras['hardware'][0]['primaryBackendNetworkComponent'] = {"router": {"id": int(private_router)}} + if public_router: + extras['hardware'][0]['primaryNetworkComponent'] = {"router": {"id": int(public_router)}} if post_uri: extras['provisionScripts'] = [post_uri] diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..5a2db4fc1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -418,6 +418,26 @@ def test_create_server_with_export(self, export_mock): self.assertIn("Successfully exported options to a template file.", result.output) export_mock.assert_called_once() + @mock.patch('SoftLayer.HardwareManager.place_order') + def test_create_server_with_router(self, order_mock): + order_mock.return_value = { + 'orderId': 98765, + 'orderDate': '2013-08-02 15:23:47' + } + + result = self.run_command(['--really', 'server', 'create', + '--size=S1270_8GB_2X1TBSATA_NORAID', + '--hostname=test', + '--domain=example.com', + '--datacenter=TEST00', + '--port-speed=100', + '--os=OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + '--router-private=123', + '--router-public=1234' + ]) + + self.assert_no_fail(result) + def test_edit_server_userdata_and_file(self): # Test both userdata and userfile at once with tempfile.NamedTemporaryFile() as userfile: diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..fb8b734ea 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -318,41 +318,29 @@ def test_generate_create_dict(self): 'port_speed': 10, 'hourly': True, 'extras': ['1_IPV6_ADDRESS'], - 'post_uri': 'http://example.com/script.php', - 'ssh_keys': [10], + 'public_router': 1111, + 'private_router': 1234 } - package = 'BARE_METAL_SERVER' - location = 'wdc07' - item_keynames = [ - '1_IP_ADDRESS', - 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', - 'REBOOT_KVM_OVER_IP', - 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', - 'BANDWIDTH_0_GB_2', - '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', - '1_IPV6_ADDRESS' - ] - hourly = True - preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' extras = { 'hardware': [{ 'domain': 'giggles.woo', 'hostname': 'unicorn', - }], - 'provisionScripts': ['http://example.com/script.php'], - 'sshKeys': [{'sshKeyIds': [10]}] + 'primaryNetworkComponent': { + "router": { + "id": 1111 + } + }, + 'primaryBackendNetworkComponent': { + "router": { + "id": 1234 + } + } + }] } data = self.hardware._generate_create_dict(**args) - - self.assertEqual(package, data['package_keyname']) - self.assertEqual(location, data['location']) - for keyname in item_keynames: - self.assertIn(keyname, data['item_keynames']) self.assertEqual(extras, data['extras']) - self.assertEqual(preset_keyname, data['preset_keyname']) - self.assertEqual(hourly, data['hourly']) def test_generate_create_dict_network_key(self): args = { From 5a046cbea66e640e3fadd8811b00170ab6708f2c Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 17 Feb 2021 16:01:50 -0400 Subject: [PATCH 0549/1582] Add unit test. --- tests/managers/hardware_tests.py | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index fb8b734ea..c709994b4 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -309,6 +309,52 @@ def test_generate_create_dict_no_regions(self): self.assertIn("Could not find valid location for: 'wdc01'", str(ex)) def test_generate_create_dict(self): + args = { + 'size': 'S1270_8GB_2X1TBSATA_NORAID', + 'hostname': 'unicorn', + 'domain': 'giggles.woo', + 'location': 'wdc07', + 'os': 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'port_speed': 10, + 'hourly': True, + 'extras': ['1_IPV6_ADDRESS'], + 'post_uri': 'http://example.com/script.php', + 'ssh_keys': [10], + } + + package = 'BARE_METAL_SERVER' + location = 'wdc07' + item_keynames = [ + '1_IP_ADDRESS', + 'UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT', + 'REBOOT_KVM_OVER_IP', + 'OS_UBUNTU_14_04_LTS_TRUSTY_TAHR_64_BIT', + 'BANDWIDTH_0_GB_2', + '10_MBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS', + '1_IPV6_ADDRESS' + ] + hourly = True + preset_keyname = 'S1270_8GB_2X1TBSATA_NORAID' + extras = { + 'hardware': [{ + 'domain': 'giggles.woo', + 'hostname': 'unicorn', + }], + 'provisionScripts': ['http://example.com/script.php'], + 'sshKeys': [{'sshKeyIds': [10]}] + } + + data = self.hardware._generate_create_dict(**args) + + self.assertEqual(package, data['package_keyname']) + self.assertEqual(location, data['location']) + for keyname in item_keynames: + self.assertIn(keyname, data['item_keynames']) + self.assertEqual(extras, data['extras']) + self.assertEqual(preset_keyname, data['preset_keyname']) + self.assertEqual(hourly, data['hourly']) + + def test_generate_create_dict_by_router_network_component(self): args = { 'size': 'S1270_8GB_2X1TBSATA_NORAID', 'hostname': 'unicorn', From f20508e44c3dd771be294df2aa2ef8709038c131 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 22 Feb 2021 18:13:46 -0400 Subject: [PATCH 0550/1582] Fix team code review comments --- SoftLayer/CLI/report/bandwidth.py | 39 ++++++++++++------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index bd651c90a..5a29bf192 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -187,9 +187,9 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='show the all bandwidth summary virtual', +@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary bare metal', +@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): @@ -233,30 +233,21 @@ def _input_to_table(item): item.get('pool') or formatting.blank(), ]) - if virtual: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing virtual collected results and then aborting.") - - elif server: - try: + try: + if virtual: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + elif server: for item in itertools.chain(_get_pooled_bandwidth(env, start, end), _get_hardware_bandwidth(env, start, end)): _input_to_table(item) - except KeyboardInterrupt: - env.err("Printing server collected results and then aborting.") - else: - for item in itertools.chain(_get_pooled_bandwidth(env, start, end), - _get_hardware_bandwidth(env, start, end), - _get_virtual_bandwidth(env, start, end)): - _input_to_table(item) - try: - pass - except KeyboardInterrupt: - env.err("Printing collected results and then aborting.") + else: + for item in itertools.chain(_get_pooled_bandwidth(env, start, end), + _get_hardware_bandwidth(env, start, end), + _get_virtual_bandwidth(env, start, end)): + _input_to_table(item) + except KeyboardInterrupt: + env.err("Printing collected results and then aborting.") env.out(env.fmt(table)) From 81afeafacb574682798b80b1a3a4b9761857cc51 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 23 Feb 2021 22:15:03 -0400 Subject: [PATCH 0551/1582] fix the tox tool --- SoftLayer/CLI/custom_types.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index 66167b68a..c5a2102a2 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -18,7 +18,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): + def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/setup.py b/setup.py index 2cb688c44..ad934b774 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION - +# pylint: disable=inconsistent-return-statements setup( name='SoftLayer', version='5.9.2', From 8bf29e4e0e9c869853c778a8b09fa9715d181b06 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 09:53:08 -0400 Subject: [PATCH 0552/1582] fix tox tool --- SoftLayer/CLI/custom_types.py | 3 ++- SoftLayer/CLI/report/bandwidth.py | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/custom_types.py b/SoftLayer/CLI/custom_types.py index c5a2102a2..e0f27b042 100644 --- a/SoftLayer/CLI/custom_types.py +++ b/SoftLayer/CLI/custom_types.py @@ -9,6 +9,7 @@ import click +# pylint: disable=inconsistent-return-statements class NetworkParamType(click.ParamType): """Validates a network parameter type and converts to a tuple. @@ -18,7 +19,7 @@ class NetworkParamType(click.ParamType): """ name = 'network' - def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements + def convert(self, value, param, ctx): try: # Inlined from python standard ipaddress module # https://docs.python.org/3/library/ipaddress.html diff --git a/SoftLayer/CLI/report/bandwidth.py b/SoftLayer/CLI/report/bandwidth.py index 5a29bf192..4ae2d0f68 100644 --- a/SoftLayer/CLI/report/bandwidth.py +++ b/SoftLayer/CLI/report/bandwidth.py @@ -175,9 +175,8 @@ def _get_virtual_bandwidth(env, start, end): @click.option( '--start', callback=_validate_datetime, - default=( - datetime.datetime.now() - datetime.timedelta(days=30) - ).strftime('%Y-%m-%d'), + default=(datetime.datetime.now() - datetime.timedelta(days=30) + ).strftime('%Y-%m-%d'), help="datetime in the format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'") @click.option( '--end', @@ -187,9 +186,11 @@ def _get_virtual_bandwidth(env, start, end): @click.option('--sortby', help='Column to sort by', default='hostname', show_default=True) -@click.option('--virtual', is_flag=True, help='Show the all bandwidth summary for each virtual server', +@click.option('--virtual', is_flag=True, + help='Show the all bandwidth summary for each virtual server', default=False) -@click.option('--server', is_flag=True, help='show the all bandwidth summary for each hardware server', +@click.option('--server', is_flag=True, + help='show the all bandwidth summary for each hardware server', default=False) @environment.pass_env def cli(env, start, end, sortby, virtual, server): From a172c908d4485ade7320f230769750aaa6afd780 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:10:37 -0400 Subject: [PATCH 0553/1582] fix tox tool --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ad934b774..ef532573a 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ LONG_DESCRIPTION = readme_file.read() else: LONG_DESCRIPTION = DESCRIPTION -# pylint: disable=inconsistent-return-statements + setup( name='SoftLayer', version='5.9.2', @@ -55,4 +55,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) +) # pylint: disable=inconsistent-return-statements From ab6962465aa11abc1cda02c0bb326858fad2c3db Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 11:29:24 -0400 Subject: [PATCH 0554/1582] fix tox tool --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef532573a..2591b055a 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ from setuptools import setup, find_packages +# pylint: disable=inconsistent-return-statements + DESCRIPTION = "A library for SoftLayer's API" if os.path.exists('README.rst'): @@ -55,4 +57,4 @@ 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], -) # pylint: disable=inconsistent-return-statements +) From 427294d8e2cc8a5ea6ee8abb544f088734ea25d3 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Feb 2021 16:57:38 -0400 Subject: [PATCH 0555/1582] fix tox tool --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 9b1259891..7f1833a4a 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -12,7 +12,7 @@ from SoftLayer.CLI import formatting -def get_api_key(client, username, secret): +def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. This will also generate an API key if one doesn't exist From 2afcd9d26901c20e7d60be97fd48d73e4f1c34f0 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 10:36:29 -0400 Subject: [PATCH 0556/1582] Allow modifying Timeout for LoadBalancers --- SoftLayer/CLI/loadbal/pools.py | 3 +++ SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 ++ tests/CLI/modules/loadbal_tests.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index 148395cd2..bfd1d48f7 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -84,6 +84,8 @@ def add(env, identifier, **args): @click.option('--method', '-m', help="Balancing Method", type=click.Choice(['ROUNDROBIN', 'LEASTCONNECTION', 'WEIGHTED_RR'])) @click.option('--connections', '-c', type=int, help="Maximum number of connections to allow.") +@click.option('--clientTimeout', '-t', type=int, + help="maximum idle time in seconds(Range: 1 to 7200).") @click.option('--sticky', '-s', is_flag=True, callback=sticky_option, help="Make sessions sticky based on source_ip.") @click.option('--sslCert', '-x', help="SSL certificate ID. See `slcli ssl list`") @environment.pass_env @@ -108,6 +110,7 @@ def edit(env, identifier, listener, **args): 'method': 'loadBalancingMethod', 'connections': 'maxConn', 'sticky': 'sessionType', + 'clienttimeout': 'clientTimeout', 'sslcert': 'tlsCertificateId' } diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 94220cdea..52158f620 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -48,6 +48,7 @@ ], 'listeners': [ { + 'clientTimeout': 15, 'defaultPool': { 'healthMonitor': { 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c8' @@ -97,6 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, + 'clientTimeout': 25, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 84866d3f5..b2da4c374 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -49,7 +49,7 @@ def test_delete_pool(self): def test_edit_pool(self): result = self.run_command(['loadbal', 'pool-edit', '111111', '370a9f12-b3be-47b3-bfa5-8e460010000', '-f 510', - '-b 256', '-c 5']) + '-b 256', '-c 5', '-t 10']) self.assert_no_fail(result) def test_add_7p(self): From 5d88288c712e2eb494665c26aee828f8103ca809 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 26 Feb 2021 17:37:59 -0400 Subject: [PATCH 0557/1582] updating --- SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 52158f620..70e8b64cb 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -98,7 +98,7 @@ 'protocolPort': 110, 'provisioningStatus': 'ACTIVE', 'tlsCertificateId': None, - 'clientTimeout': 25, + 'clientTimeout': 30, 'uuid': 'a509723d-a3cb-4ae4-bc5b-5ecf04f890ff'} ], 'members': [ From 956d7fa6baef8b5c584663b488f29ff750d6dbfc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 16:55:39 -0600 Subject: [PATCH 0558/1582] #1425 checking termLength when ordering --- SoftLayer/managers/ordering.py | 11 ++++++--- tests/managers/ordering_tests.py | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..dcfb4e186 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -406,11 +406,16 @@ def get_price_id_list(self, package_keyname, item_keynames, core=None): return prices @staticmethod - def get_item_price_id(core, prices): - """get item price id""" + def get_item_price_id(core, prices, term=0): + """get item price id + + core: None or a number to match against capacityRestrictionType + prices: list of SoftLayer_Product_Item_Price + term: int to match against SoftLayer_Product_Item_Price.termLength + """ price_id = None for price in prices: - if not price['locationGroupId']: + if not price['locationGroupId'] and price.get('termLength', 0) in {term, '', None}: restriction = price.get('capacityRestrictionType', False) # There is a price restriction. Make sure the price is within the restriction if restriction and core is not None: diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..48cf38735 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -841,3 +841,45 @@ def test_resolve_location_name_invalid(self): def test_resolve_location_name_not_exist(self): exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) + + # https://github.com/softlayer/softlayer-python/issues/1425 + # Issues relating to checking prices based of the price.term relationship + def test_issues1425_zeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 0 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test None termLength + price2['termLength'] = None + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + # Test '' termLength + price2['termLength'] = '' + price_id = self.ordering.get_item_price_id("8", [price2, price1]) + self.assertEqual(45678, price_id) + + def test_issues1425_nonzeroterm(self): + category1 = {'categoryCode': 'cat1'} + price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} + price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} + + # Test 36 termLength + price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) + self.assertEqual(1234, price_id) + + # Test None-existing price for term + price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) + self.assertEqual(None, price_id) \ No newline at end of file From 3b6aae8ca03a8378cb3a0697480f0fde24e0d6be Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 26 Feb 2021 17:01:07 -0600 Subject: [PATCH 0559/1582] tox fixes --- tests/managers/ordering_tests.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 48cf38735..7adcd684f 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -847,11 +847,11 @@ def test_resolve_location_name_not_exist(self): def test_issues1425_zeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 0 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1]) @@ -870,11 +870,11 @@ def test_issues1425_zeroterm(self): def test_issues1425_nonzeroterm(self): category1 = {'categoryCode': 'cat1'} price1 = {'id': 1234, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 36} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 36} price2 = {'id': 45678, 'locationGroupId': '', "capacityRestrictionMaximum": "16", - "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", - 'categories': [category1], 'termLength': 0} + "capacityRestrictionMinimum": "1", "capacityRestrictionType": "STORAGE_SPACE", + 'categories': [category1], 'termLength': 0} # Test 36 termLength price_id = self.ordering.get_item_price_id("8", [price2, price1], 36) @@ -882,4 +882,4 @@ def test_issues1425_nonzeroterm(self): # Test None-existing price for term price_id = self.ordering.get_item_price_id("8", [price2, price1], 37) - self.assertEqual(None, price_id) \ No newline at end of file + self.assertEqual(None, price_id) From b9c759566b2ecddafc4730b47dfa3fc885f00a85 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 19:51:20 -0600 Subject: [PATCH 0560/1582] Add testing and support for python 3.9. Remove testtools as a test dependency. It's not used except for a version skip that is easy to replace. testtools still relies on unittest2, a python 2-ism that generates warnings in python 3.9. Fix the snapcraft release by updating to a newer snapcraft action that fixes the 'add-path' error. --- .github/workflows/release.yml | 46 +++++++------- .github/workflows/tests.yml | 108 ++++++++++++++++---------------- README.rst | 4 +- setup.py | 1 + tests/CLI/modules/user_tests.py | 5 +- tools/test-requirements.txt | 1 - tox.ini | 2 +- 7 files changed, 84 insertions(+), 83 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36ef10414..9b244a86b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,23 @@ -name: Release - -on: - release: - types: [published] - -jobs: - release: - runs-on: ubuntu-latest - strategy: - matrix: - arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] - steps: - - name: Install Snapcraft - uses: samuelmeuli/action-snapcraft@v1.1.1 - with: - snapcraft_token: ${{ secrets.snapcraft_token }} - - name: Push to stable - run: | - VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` - echo Publishing $VERSION on ${{ matrix.arch }} - snapcraft release slcli $VERSION stable - +name: Release + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-18.04 + strategy: + matrix: + arch: ['armhf','amd64','arm64','ppc64el','s390x','i386'] + steps: + - name: Install Snapcraft + uses: samuelmeuli/action-snapcraft@v1.2.0 + with: + snapcraft_token: ${{ secrets.snapcraft_token }} + - name: Push to stable + run: | + VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` + echo Publishing $VERSION on ${{ matrix.arch }} + snapcraft release slcli $VERSION stable + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7aa408ed8..7bc787791 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,54 +1,54 @@ -name: Tests - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.5,3.6,3.7,3.8] - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Test - run: tox -e py - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Coverage - run: tox -e coverage - analysis: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: 3.8 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r tools/test-requirements.txt - - name: Tox Analysis - run: tox -e analysis \ No newline at end of file +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5,3.6,3.7,3.8,3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Test + run: tox -e py + coverage: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Coverage + run: tox -e coverage + analysis: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r tools/test-requirements.txt + - name: Tox Analysis + run: tox -e analysis diff --git a/README.rst b/README.rst index 4f741a5d0..75e5d6f54 100644 --- a/README.rst +++ b/README.rst @@ -127,7 +127,7 @@ If you are using the library directly in python, you can do something like this. System Requirements ------------------- -* Python 3.5, 3.6, 3.7, or 3.8. +* Python 3.5, 3.6, 3.7, 3.8, or 3.9. * A valid SoftLayer API username and key. * A connection to SoftLayer's private network is required to use our private network API endpoints. @@ -150,6 +150,6 @@ Python Packages Copyright --------- -This software is Copyright (c) 2016-2019 SoftLayer Technologies, Inc. +This software is Copyright (c) 2016-2021 SoftLayer Technologies, Inc. See the bundled LICENSE file for more information. diff --git a/setup.py b/setup.py index 2591b055a..7bbc3ebe5 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,7 @@ 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index b6723a2b2..246a5b0a2 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,6 @@ import sys import mock -import testtools from SoftLayer import testing @@ -168,10 +167,12 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @testtools.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): + if sys.version_info < (3, 6): + self.skipTest("Secrets module only exists in version 3.6+") + secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) diff --git a/tools/test-requirements.txt b/tools/test-requirements.txt index d417e04c1..0f1ec684c 100644 --- a/tools/test-requirements.txt +++ b/tools/test-requirements.txt @@ -4,7 +4,6 @@ pytest pytest-cov mock sphinx -testtools ptable >= 0.9.2 click >= 7 requests >= 2.20.0 diff --git a/tox.ini b/tox.ini index c9b3b5e72..b1fa2c870 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,pypy3,analysis,coverage,docs +envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From f019f3dd68447b95aacce6978b86f21d2b72ddb3 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:12:20 -0600 Subject: [PATCH 0561/1582] Use testtools.SkipIf instead so that secrets mocking isn't broken for python 3.5. --- tests/CLI/modules/user_tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 246a5b0a2..3693d32b5 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -6,6 +6,7 @@ """ import json import sys +import unittest import mock @@ -167,12 +168,10 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) + @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): - if sys.version_info < (3, 6): - self.skipTest("Secrets module only exists in version 3.6+") - secrets.return_value = 'Q' confirm_mock.return_value = True result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'generate']) From 8d73349a740a9887ad7cc97acf2057b32aff58ea Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Mon, 1 Mar 2021 20:16:17 -0600 Subject: [PATCH 0562/1582] Fix casing --- tests/CLI/modules/user_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 3693d32b5..2f4c1c978 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -168,7 +168,7 @@ def test_create_user_no_confirm(self, confirm_mock): result = self.run_command(['user', 'create', 'test', '-e', 'test@us.ibm.com', '-p', 'testword']) self.assertEqual(result.exit_code, 2) - @unittest.SkipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") + @unittest.skipIf(sys.version_info < (3, 6), "Secrets module only exists in version 3.6+") @mock.patch('secrets.choice') @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_user_generate_password_36(self, confirm_mock, secrets): From fb138c6cc95a4c0eefcf0f393d67b04fb68c895d Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 2 Mar 2021 18:35:29 -0400 Subject: [PATCH 0563/1582] #1430 refactor Ordering verify_quote cleaning empty or None fields logic --- SoftLayer/managers/ordering.py | 6 +++--- tests/managers/ordering_tests.py | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index e1cbb6f31..5faf8af60 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -218,11 +218,11 @@ def verify_quote(self, quote_id, extra): container = self.generate_order_template(quote_id, extra) clean_container = {} - # There are a few fields that wil cause exceptions in the XML endpoing if you send in '' - # reservedCapacityId and hostId specifically. But we clean all just to be safe. + # There are a few fields that wil cause exceptions in the XML endpoint if you send in '', + # or None in Rest endpoint (e.g. reservedCapacityId, hostId). But we clean all just to be safe. # This for some reason is only a problem on verify_quote. for key in container.keys(): - if container.get(key) != '': + if container.get(key): clean_container[key] = container[key] return self.client.call('SoftLayer_Billing_Order_Quote', 'verifyOrder', clean_container, id=quote_id) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f42532c7b..e790028ba 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -713,7 +713,8 @@ def test_clean_quote_verify(self): 'hostname': 'test1', 'domain': 'example.com' }], - 'testProperty': '' + 'testPropertyEmpty': '', + 'testPropertyNone': None } result = self.ordering.verify_quote(1234, extras) @@ -721,7 +722,8 @@ def test_clean_quote_verify(self): self.assert_called_with('SoftLayer_Billing_Order_Quote', 'verifyOrder') call = self.calls('SoftLayer_Billing_Order_Quote', 'verifyOrder')[0] order_container = call.args[0] - self.assertNotIn('testProperty', order_container) + self.assertNotIn('testPropertyEmpty', order_container) + self.assertNotIn('testPropertyNone', order_container) self.assertNotIn('reservedCapacityId', order_container) def test_get_item_capacity_core(self): From e4c72b8562c8a82c3fa3adf0a26aa6ff8f8f0b8c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:24:42 -0400 Subject: [PATCH 0564/1582] Add routers for each DC in `slcli hw create-options` --- SoftLayer/CLI/hardware/create_options.py | 29 +++++++++++++++++++++++- SoftLayer/fixtures/SoftLayer_Account.py | 18 +++++++++++++++ SoftLayer/managers/account.py | 10 ++++++++ tests/managers/account_tests.py | 4 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 4c7723fb9..5231904cd 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -5,6 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.managers import account from SoftLayer.managers import hardware @@ -18,8 +19,18 @@ def cli(env, prices, location=None): """Server order options for a given chassis.""" hardware_manager = hardware.HardwareManager(env.client) + account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) + _filter = '' + if location: + _filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } + + routers = account_manager.get_routers(_filter) tables = [] # Datacenters @@ -34,6 +45,7 @@ def cli(env, prices, location=None): tables.append(_os_prices_table(options['operating_systems'], prices)) tables.append(_port_speed_prices_table(options['port_speeds'], prices)) tables.append(_extras_prices_table(options['extras'], prices)) + tables.append(_get_routers(routers)) # since this is multiple tables, this is required for a valid JSON object to be rendered. env.fout(formatting.listing(tables, separator='\n')) @@ -50,7 +62,7 @@ def _preset_prices_table(sizes, prices=False): for size in sizes: if size.get('hourlyRecurringFee', 0) + size.get('recurringFee', 0) + 1 > 0: table.add_row([size['name'], size['key'], "%.4f" % size['hourlyRecurringFee'], - "%.4f" % size['recurringFee']]) + "%.4f" % size['recurringFee']]) else: table = formatting.Table(['Size', 'Value'], title="Sizes") for size in sizes: @@ -146,3 +158,18 @@ def _get_price_data(price, item): if item in price: result = price[item] return result + + +def _get_routers(routers): + """Get all routers information + + :param routers: Routers data + """ + + table = formatting.Table(["id", "hostname", "name"], title='Routers') + for router in routers: + table.add_row([router['id'], + router['hostname'], + router['topLevelLocation']['longName'], ]) + table.align = 'l' + return table diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..3c82b3da3 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,21 @@ "resourceTableId": 777777 } ] + +getRouters = [ + { + "accountId": 1, + "bareMetalInstanceFlag": 0, + "domain": "softlayer.com", + "fullyQualifiedDomainName": "fcr01a.ams01.softlayer.com", + "hardwareStatusId": 5, + "hostname": "fcr01a.ams01", + "id": 123456, + "serviceProviderId": 1, + "topLevelLocation": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + } + }] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 45ac3ad52..6b8c9f031 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -276,3 +276,13 @@ def get_account_all_billing_orders(self, limit=100, mask=None): """ return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) + + def get_routers(self, mask=None, _filter=None): + """Gets all the routers currently active on the account + + :param string mask: Object Mask + :param string _filter: Object filter + :returns: Routers + """ + + return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index b051e5ee7..c5d2edf95 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -149,3 +149,7 @@ def test_get_item_details_with_invoice_item_id(self): self.manager.get_item_detail(123456) self.assert_called_with('SoftLayer_Billing_Item', 'getObject', identifier=123456) self.assert_called_with('SoftLayer_Billing_Invoice_Item', 'getBillingItem', identifier=123456) + + def test_get_routers(self): + self.manager.get_routers() + self.assert_called_with("SoftLayer_Account", "getRouters") From 78400f19bf15cbdb1cab499ff65548cb6d59629a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:47:52 -0400 Subject: [PATCH 0565/1582] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 12 +++--------- SoftLayer/managers/account.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 5231904cd..186a6fe44 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -22,15 +22,9 @@ def cli(env, prices, location=None): account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - _filter = '' - if location: - _filter = { - 'routers': { - 'topLevelLocation': {'name': {'operation': location}} - } - } - - routers = account_manager.get_routers(_filter) + + + routers = account_manager.get_routers(location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 6b8c9f031..d008b92a3 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,12 +277,19 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, _filter=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask - :param string _filter: Object filter + :param string location: location string :returns: Routers """ + object_filter = '' + if location: + object_filter = { + 'routers': { + 'topLevelLocation': {'name': {'operation': location}} + } + } - return self.client['SoftLayer_Account'].getRouters(filter=_filter, mask=mask) + return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) From 5d338a529322364c526e5ed9993214bf025439bc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 3 Mar 2021 15:50:21 -0600 Subject: [PATCH 0566/1582] v5.9.3 Release notes and updates --- CHANGELOG.md | 22 ++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86588d91..70ab66753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Change Log + +## [5.9.3] - 2021-03-03 +https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 + +#### New Commands +- `slcli file|block disaster-recovery-failover` #1407 + +#### Improvements +- Unit testing for large integers #1403 +- Add Multi factor authentication to users list #1408 +- Add pagination to object storage list accounts. #1411 +- Add username lookup to slcli object-storage credential #1415 +- Add IOPs data to slcli block volume-list. #1418 +- Add 2FA and classic APIKeys fields to slcli user list as default values #1421 +- Add a flags in the report bandwidth #1420 +- Add the option network component by router to slcli hw create. #1422 +- Add slcli vs create by router data. #1414 +- Add testing and support for python 3.9. #1429 +- Checking for TermLength on prices #1428 + + + ## [5.9.2] - 2020-12-03 https://github.com/softlayer/softlayer-python/compare/v5.9.1...v5.9.2 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index a09e85706..b10b164d0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.2' +VERSION = 'v5.9.3' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 7bbc3ebe5..6d51d38ce 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.2', + version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer Technologies, Inc.', From fa9da6a1dbd884212e65f8ba3a3564070e086b2c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 3 Mar 2021 17:56:49 -0400 Subject: [PATCH 0567/1582] fix tox tool --- SoftLayer/CLI/hardware/create_options.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 186a6fe44..7d104d877 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,9 +21,6 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - - - routers = account_manager.get_routers(location) tables = [] From f4fee95a9a83dd746388c9e37c9df1bc521febe7 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 9 Mar 2021 15:14:54 -0400 Subject: [PATCH 0568/1582] Add preset datatype in `slcli virtual detail` --- SoftLayer/CLI/virt/detail.py | 1 + SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 37 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 94c0c7994..731df19ea 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..5a3bcc105 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -28,7 +28,8 @@ 'userRecord': { 'username': 'chechu', } - } + }, + 'preset': {'keyName': 'B1_8X16X100'} } }, 'datacenter': {'id': 50, 'name': 'TEST00', @@ -480,16 +481,16 @@ "categoryCode": "guest_disk0", "id": 81 }}, { - "description": "250 GB (SAN)", - "attributes": [ - { - "id": 198, - "attributeTypeKeyName": "SAN_DISK" - }], - "itemCategory": { - "categoryCode": "guest_disk0", - "id": 89 - }}], + "description": "250 GB (SAN)", + "attributes": [ + { + "id": 198, + "attributeTypeKeyName": "SAN_DISK" + }], + "itemCategory": { + "categoryCode": "guest_disk0", + "id": 89 + }}], 'guest_core': [{ "description": "4 x 2.0 GHz or higher Cores (Dedicated)", "attributes": [], @@ -497,13 +498,13 @@ "categoryCode": "guest_core", "id": 80 }}, - { - "description": "8 x 2.0 GHz or higher Cores", - "attributes": [], - "itemCategory": { - "categoryCode": "guest_core", - "id": 90 - }}] + { + "description": "8 x 2.0 GHz or higher Cores", + "attributes": [], + "itemCategory": { + "categoryCode": "guest_core", + "id": 90 + }}] } getReverseDomainRecords = [{ From c400ab560033f9282634197bbc3fb1dd52633757 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:02:54 -0400 Subject: [PATCH 0569/1582] Adding upgrade option to slcli hw. Adding cli unit test. --- SoftLayer/CLI/hardware/upgrade.py | 47 ++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Hardware_Server.py | 76 ++++++++++ SoftLayer/managers/hardware.py | 139 ++++++++++++++++++ tests/CLI/modules/server_tests.py | 27 ++++ 5 files changed, 290 insertions(+) create mode 100644 SoftLayer/CLI/hardware/upgrade.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py new file mode 100644 index 000000000..d766000ab --- /dev/null +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -0,0 +1,47 @@ +"""Upgrade a hardware server.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--memory', type=click.INT, help="Memory Size in GB") +@click.option('--network', help="Network port speed in Mbps", + default=None, + type=click.Choice(['100', '100 Redundant', '100 Dual', + '1000', '1000 Redundant', '1000 Dual', + '10000', '10000 Redundant', '10000 Dual']) + ) +@click.option('--drive-controller', + help="Drive Controller", + default=None, + type=click.Choice(['Non-RAID', 'RAID'])) +@click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") +@environment.pass_env +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): + """Upgrade a Hardware Server.""" + + mgr = SoftLayer.HardwareManager(env.client) + + if not any([memory, network, drive_controller, public_bandwidth]): + raise exceptions.ArgumentError("Must provide " + " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + + hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') + if not test: + if not (env.skip_confirmations or formatting.confirm( + "This action will incur charges on your account. Continue?")): + raise exceptions.CLIAbort('Aborted') + + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, test=test): + raise exceptions.CLIAbort('Hardware Server Upgrade Failed') + env.fout('Successfully Upgraded.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 40c4bd6d1..eee8fe314 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), + ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 5c26d20da..a28d0fc13 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -6,6 +6,9 @@ 'billingItem': { 'id': 6327, 'recurringFee': 1.54, + 'package': { + 'id': 911 + }, 'nextInvoiceTotalRecurringAmount': 16.08, 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, @@ -262,3 +265,76 @@ } } } + +getUpgradeItemPrices = [ + { + "id": 21525, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "port_speed", + "id": 26, + "name": "Uplink Port Speeds", + } + ], + "item": { + "capacity": "10000", + "description": "10 Gbps Redundant Public & Private Network Uplinks", + "id": 4342, + "keyName": "10_GBPS_REDUNDANT_PUBLIC_PRIVATE_NETWORK_UPLINKS" + } + }, + { + "hourlyRecurringFee": ".247", + "id": 209391, + "recurringFee": "164", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG" + } + }, + { + "hourlyRecurringFee": ".068", + "id": 22482, + "recurringFee": "50", + "categories": [ + { + "categoryCode": "disk_controller", + "id": 11, + "name": "Disk Controller", + } + ], + "item": { + "capacity": "0", + "description": "RAID", + "id": 4478, + "keyName": "DISK_CONTROLLER_RAID", + } + }, + { + "id": 50357, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "bandwidth", + "id": 10, + "name": "Public Bandwidth", + } + ], + "item": { + "capacity": "500", + "description": "500 GB Bandwidth Allotment", + "id": 6177, + "keyName": "BANDWIDTH_500_GB" + } + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 53939f2e1..5eeac0709 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -5,11 +5,13 @@ :license: MIT, see LICENSE for more details. """ +import datetime import logging import socket import time from SoftLayer.decoration import retry +from SoftLayer import exceptions from SoftLayer.exceptions import SoftLayerError from SoftLayer.managers import ordering from SoftLayer.managers.ticket import TicketManager @@ -780,6 +782,143 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def upgrade(self, instance_id, memory=None, + nic_speed=None, drive_controller=None, + public_bandwidth=None, test=False): + """Upgrades a hardware server instance. + + :param int instance_id: Instance id of the hardware server to be upgraded. + :param string memory: Memory size. + :param string nic_speed: Network Port Speed data. + :param string drive_controller: Drive Controller data. + :param string public_bandwidth: Public keyName data. + :param bool test: Test option to verify the request. + + :returns: bool + """ + upgrade_prices = self._get_upgrade_prices(instance_id) + prices = [] + data = {} + + if memory: + data['memory'] = memory + if nic_speed: + data['nic_speed'] = nic_speed + if drive_controller: + data['disk_controller'] = drive_controller + if public_bandwidth: + data['bandwidth'] = public_bandwidth + + server_response = self.get_instance(instance_id) + package_id = server_response['billingItem']['package']['id'] + + maintenance_window = datetime.datetime.now(utils.UTC()) + order = { + 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', + 'properties': [{ + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }], + 'hardware': [{'id': int(instance_id)}], + 'packageId': package_id + } + + for option, value in data.items(): + price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) + if not price_id: + # Every option provided is expected to have a price + raise exceptions.SoftLayerError( + "Unable to find %s option with value %s" % (option, value)) + + prices.append({'id': price_id}) + + order['prices'] = prices + + if prices: + if test: + self.client['Product_Order'].verifyOrder(order) + else: + self.client['Product_Order'].placeOrder(order) + return True + return False + + @retry(logger=LOGGER) + def get_instance(self, instance_id): + """Get details about a hardware server instance. + + :param int instance_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified instance. + """ + mask = [ + 'billingItem[id,package[id,keyName]]' + ] + mask = "mask[%s]" % ','.join(mask) + + return self.hardware.getObject(id=instance_id, mask=mask) + + def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + """Following Method gets all the price ids related to upgrading a VS. + + :param int instance_id: Instance id of the VS to be upgraded + + :returns: list + """ + mask = [ + 'id', + 'locationGroupId', + 'categories[name,id,categoryCode]', + 'item[keyName,description,capacity,units]' + ] + mask = "mask[%s]" % ','.join(mask) + return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + + @staticmethod + def _get_prices_for_upgrade_option(upgrade_prices, option, value): + """Find the price id for the option and value to upgrade. This + + :param list upgrade_prices: Contains all the prices related to a + hardware server upgrade. + :param string option: Describes type of parameter to be upgraded + :param value: The value of the parameter to be upgraded + """ + + option_category = { + 'memory': 'ram', + 'nic_speed': 'port_speed', + 'disk_controller': 'disk_controller', + 'bandwidth': 'bandwidth' + } + category_code = option_category.get(option) + + for price in upgrade_prices: + if price.get('categories') is None or price.get('item') is None: + continue + + product = price.get('item') + for category in price.get('categories'): + if not (category.get('categoryCode') == category_code): + continue + + if option == 'disk_controller': + if value == product.get('description'): + return price.get('id') + elif option == 'nic_speed': + if value.isdigit(): + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + split_nic_speed = value.split(" ") + if str(product.get('capacity')) == str(split_nic_speed[0]) and \ + split_nic_speed[1] in product.get("description"): + return price.get('id') + elif option == 'bandwidth': + if str(product.get('capacity')) == str(value): + return price.get('id') + else: + if str(product.get('capacity')) == str(value): + return price.get('id') + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 9b88c81f2..efd7eb32b 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -883,3 +883,30 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + def test_upgrade_no_options(self, ): + result = self.run_command(['hw', 'upgrade', '100']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.ArgumentError) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_aborted(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--memory=1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_test(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', + '--drive-controller=RAID', '--network=10000 Redundant']) + self.assert_no_fail(result) + From 8addedbf3225593590f205b559378ea1d6472554 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:38:20 -0400 Subject: [PATCH 0570/1582] Adding unit test. --- SoftLayer/managers/hardware.py | 4 ++-- tests/managers/hardware_tests.py | 41 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5eeac0709..fc083995c 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -788,10 +788,10 @@ def upgrade(self, instance_id, memory=None, """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. - :param string memory: Memory size. + :param int memory: Memory size. :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. - :param string public_bandwidth: Public keyName data. + :param int public_bandwidth: Public keyName data. :param bool test: Test option to verify the request. :returns: bool diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 062cafc30..e44301c73 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -807,6 +807,47 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_get_price_id_memory_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(99, result) + + def test_get_price_id_mismatch_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'ram1'}], 'item': {'capacity': 1}, 'id': 90}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 2}, 'id': 91}, + {'categories': [{'categoryCode': 'ram'}], 'item': {'capacity': 1}, 'id': 92}, + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) + self.assertEqual(92, result) + + def test_upgrade(self): + result = self.hardware.upgrade(1, memory=32) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'], [{'id': 209391}]) + + def test_upgrade_blank(self): + result = self.hardware.upgrade(1) + + self.assertEqual(result, False) + self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) + + def test_upgrade_full(self): + result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", + public_bandwidth=500, test=False) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + class HardwareHelperTests(testing.TestCase): From d7289bb61843e8f3a4aade783fd701fb3f3a921f Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 19:45:26 -0400 Subject: [PATCH 0571/1582] Adding documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..e4d99998c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.upgrade:cli + :prog: hardware upgrade + :show-nested: From 03d032933a31dbe41dfb0d83cbaa043820e98c07 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:10:53 -0400 Subject: [PATCH 0572/1582] Adding documentation. Fix tests. --- tests/CLI/modules/server_tests.py | 1 - tests/managers/hardware_tests.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index efd7eb32b..803d4aab1 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -909,4 +909,3 @@ def test_upgrade(self, confirm_mock): result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) - diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e44301c73..4aa3410b8 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -846,7 +846,10 @@ def test_upgrade_full(self): self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] - self.assertEqual([{'id': 209391}, {'id': 21525}, {'id': 22482}, {'id': 50357}], order_container['prices']) + self.assertIn({'id': 209391}, order_container['prices']) + self.assertIn({'id': 21525}, order_container['prices']) + self.assertIn({'id': 22482}, order_container['prices']) + self.assertIn({'id': 50357}, order_container['prices']) class HardwareHelperTests(testing.TestCase): From 556b1cc1d9290a5fbd67d3ffb97aaf9562d532bf Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 20:41:00 -0400 Subject: [PATCH 0573/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fc083995c..d0436e54f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -20,7 +20,7 @@ LOGGER = logging.getLogger(__name__) # Invalid names are ignored due to long method names and short argument names -# pylint: disable=invalid-name, no-self-use +# pylint: disable=invalid-name, no-self-use, too-many-lines EXTRA_CATEGORIES = ['pri_ipv6_addresses', 'static_ipv6_addresses', @@ -881,6 +881,8 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded :param value: The value of the parameter to be upgraded + + :returns: int """ option_category = { @@ -897,7 +899,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): product = price.get('item') for category in price.get('categories'): - if not (category.get('categoryCode') == category_code): + if not category.get('categoryCode') == category_code: continue if option == 'disk_controller': From 9741084bbf95d4397815baad71df5838342561b4 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:22:31 -0400 Subject: [PATCH 0574/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d0436e54f..5baedd314 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value: The value of the parameter to be upgraded + :param value value: The value of the parameter to be upgraded :returns: int """ From f8a418af42bb1d7c4b399fadc3a02e65e3ff5b20 Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:29:36 -0400 Subject: [PATCH 0575/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5baedd314..b303fb5f6 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -880,7 +880,6 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): :param list upgrade_prices: Contains all the prices related to a hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :param value value: The value of the parameter to be upgraded :returns: int """ From 208693481aeaa18daf8887831f1d89f6e6a9379d Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:40:55 -0400 Subject: [PATCH 0576/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index b303fb5f6..7aef731f8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,9 +881,9 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: int + :returns: A item price id. """ - + price_id = None option_category = { 'memory': 'ram', 'nic_speed': 'port_speed', @@ -903,22 +903,24 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): if option == 'disk_controller': if value == product.get('description'): - return price.get('id') + price_id = price.get('id') elif option == 'nic_speed': if value.isdigit(): if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: split_nic_speed = value.split(" ") if str(product.get('capacity')) == str(split_nic_speed[0]) and \ split_nic_speed[1] in product.get("description"): - return price.get('id') + price_id = price.get('id') elif option == 'bandwidth': if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') else: if str(product.get('capacity')) == str(value): - return price.get('id') + price_id = price.get('id') + + return price_id def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): From 21a5db744e5e3b239d6fafb1c50b7acdc4301b4a Mon Sep 17 00:00:00 2001 From: Fernando Ojeda Date: Wed, 10 Mar 2021 21:42:26 -0400 Subject: [PATCH 0577/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 7aef731f8..ba4494a3e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -881,7 +881,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): hardware server upgrade. :param string option: Describes type of parameter to be upgraded - :returns: A item price id. + :return: int. """ price_id = None option_category = { From 4443f0b3d796e07f44b55cec7d64b11aff31bc68 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 15 Mar 2021 09:33:37 -0400 Subject: [PATCH 0578/1582] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 731df19ea..0829f782f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,7 +69,10 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) - table.add_row(['preset', result['billingItem']['orderItem']['preset']['keyName']]) + table.add_row(['preset', utils.lookup(result, 'billingItem', + 'orderItem', + 'preset', + 'keyName') or {}]) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 60a6febaa00bf86f370bf76da7f7a2a0454f4b16 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 16 Mar 2021 09:07:09 -0400 Subject: [PATCH 0579/1582] Fix Teams code review comments --- SoftLayer/CLI/virt/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 0829f782f..01a66cc9f 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -72,7 +72,7 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', - 'keyName') or {}]) + 'keyName') or '-']) table.add_row(_get_owner_row(result)) table.add_row(_get_vlan_table(result)) From 9c9d4cf1cb62ffa763d3d830477d7d352fa90818 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 17 Mar 2021 17:08:51 -0400 Subject: [PATCH 0580/1582] Add the authorize block and file storage to a hw. --- SoftLayer/CLI/hardware/authorize_storage.py | 25 +++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 19 ++++++++++++++++ SoftLayer/fixtures/SoftLayer_Hardware.py | 2 ++ SoftLayer/managers/hardware.py | 24 ++++++++++++++++++++ tests/CLI/modules/server_tests.py | 11 +++++++++ tests/managers/hardware_tests.py | 5 +++++ 7 files changed, 87 insertions(+) create mode 100644 SoftLayer/CLI/hardware/authorize_storage.py diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py new file mode 100644 index 000000000..013896883 --- /dev/null +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -0,0 +1,25 @@ +"""Authorize File or Block Storage to a Hardware Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the hardware server") +@environment.pass_env +def cli(env, identifier, username_storage): + """Authorize File or Block Storage to a Hardware Server. + """ + hardware = SoftLayer.HardwareManager(env.client) + hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') + + if not hardware.authorize_storage(hardware_id, username_storage): + raise exceptions.CLIAbort('Authorize Storage Failed') + env.fout('Successfully Storage Added.') diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..74a87c10b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -256,6 +256,7 @@ ('hardware:rescue', 'SoftLayer.CLI.hardware.power:rescue'), ('hardware:ready', 'SoftLayer.CLI.hardware.ready:cli'), ('hardware:toggle-ipmi', 'SoftLayer.CLI.hardware.toggle_ipmi:cli'), + ('hardware:authorize-storage', 'SoftLayer.CLI.hardware.authorize_storage:cli'), ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 123e0bc47..75e565004 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1020,3 +1020,22 @@ "resourceTableId": 777777 } ] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index cb902b556..fd6bf9535 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -57,3 +57,5 @@ {'tag': {'name': 'a tag'}} ], } + +allowAccessToNetworkStorageList = True diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ecf1a89c2..34a6c16cf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -786,6 +786,30 @@ def get_hardware_item_prices(self, location): return self.client.call('SoftLayer_Product_Package', 'getItemPrices', mask=object_mask, filter=object_filter, id=package['id']) + def authorize_storage(self, hardware_id, username_storage): + """Authorize File or Block Storage to a Hardware Server. + + :param int hardware_id: Hardware server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('Hardware', 'allowAccessToNetworkStorageList', + storage_template, id=hardware_id) + + return result + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 5a2db4fc1..3434643a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -903,3 +903,14 @@ def test_hardware_guests_empty(self): result = self.run_command(['hw', 'guests', '123456']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + def test_authorize_hw(self): + result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index c709994b4..e1e3f8cfd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -841,6 +841,11 @@ def test_get_hardware_guests(self): self.assertEqual("NSX-T Manager", result[0]['hostname']) + def test_authorize_storage(self): + options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + class HardwareHelperTests(testing.TestCase): From f4b835dfc340f068646db18dbb19fb76008fefa6 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 10:45:01 -0400 Subject: [PATCH 0581/1582] Add documentation. --- docs/cli/hardware.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index f7691e700..490a6608c 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -119,3 +119,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.guests:cli :prog: hardware guests :show-nested: + +.. click:: SoftLayer.CLI.hardware.authorize_storage:cli + :prog: hardware authorize-storage + :show-nested: From ac8a4b492080567c6fa938b28379fde8fa4776c5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 18 Mar 2021 14:54:32 -0400 Subject: [PATCH 0582/1582] Fix method documentation. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index ba4494a3e..42fd25ba0 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -858,9 +858,9 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): - """Following Method gets all the price ids related to upgrading a VS. + """Following Method gets all the price ids related to upgrading a Hardware Server. - :param int instance_id: Instance id of the VS to be upgraded + :param int instance_id: Instance id of the Hardware Server to be upgraded. :returns: list """ From 1392433cb27e4bdb3d9581ddef08b2f6e23db3c6 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Thu, 18 Mar 2021 18:46:54 -0400 Subject: [PATCH 0583/1582] Update authorize_storage.py Fix docstring. --- SoftLayer/CLI/hardware/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/authorize_storage.py b/SoftLayer/CLI/hardware/authorize_storage.py index 013896883..69b4ba273 100644 --- a/SoftLayer/CLI/hardware/authorize_storage.py +++ b/SoftLayer/CLI/hardware/authorize_storage.py @@ -15,8 +15,7 @@ help="The storage username to be added to the hardware server") @environment.pass_env def cli(env, identifier, username_storage): - """Authorize File or Block Storage to a Hardware Server. - """ + """Authorize File or Block Storage to a Hardware Server.""" hardware = SoftLayer.HardwareManager(env.client) hardware_id = helpers.resolve_id(hardware.resolve_ids, identifier, 'hardware') From 605cab1eb1464919582c0e7fd33a33e8e7e93184 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 12:59:43 -0400 Subject: [PATCH 0584/1582] Fix exception storage username. --- SoftLayer/managers/hardware.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a6c16cf..309a019b4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,6 +797,10 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) storage_template = [ { From acde527815b9955a8a3969e8a366b3e06c8ab125 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:02:42 -0400 Subject: [PATCH 0585/1582] Add a unit test to avoid storage username ex. --- tests/CLI/modules/server_tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3434643a2..6c746c9d9 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,6 +910,16 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_hw_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['hw', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) From 68f2a99f7e92b03392b9da3c05ec46d998184884 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Fri, 19 Mar 2021 13:06:49 -0400 Subject: [PATCH 0586/1582] Add a unit test to manager for storage username. --- tests/managers/hardware_tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index e1e3f8cfd..7a008e4fd 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,6 +845,13 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.hardware.authorize_storage, + 1234, "#") class HardwareHelperTests(testing.TestCase): From 239452a9c1c24511e5c7c6f175835da3331d1dff Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 13:19:55 -0400 Subject: [PATCH 0587/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 309a019b4..7f0ba1baf 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -797,7 +797,7 @@ def authorize_storage(self, hardware_id, username_storage): _filter = {"networkStorage": {"username": {"operation": username_storage}}} storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) - + if len(storage_result) == 0: raise SoftLayerError("The Storage with username: %s was not found, please" " enter a valid storage username" % username_storage) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 6c746c9d9..3d3b440f5 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -910,7 +910,7 @@ def test_authorize_hw_no_confirm(self, confirm_mock): result = self.run_command(['hw', 'authorize-storage', '-u', '1234']) self.assertEqual(result.exit_code, 2) - + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_authorize_hw_empty(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 7a008e4fd..e39846e31 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -845,7 +845,7 @@ def test_authorize_storage(self): options = self.hardware.authorize_storage(1234, "SL01SEL301234-11") self.assertEqual(True, options) - + def test_authorize_storage_empty(self): mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') mock.return_value = [] From 3853a1f45d7da5480247685b6ae7c2996882951e Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 19 Mar 2021 15:43:34 -0400 Subject: [PATCH 0588/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 1 + tests/CLI/modules/server_tests.py | 1 + 2 files changed, 2 insertions(+) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8cb4f7acb..e13394355 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -956,6 +956,7 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): return price_id + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index f92fdf9d7..de7ccd95e 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -923,6 +923,7 @@ def test_authorize_hw_empty(self, confirm_mock): def test_authorize_hw(self): result = self.run_command(['hw', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) def test_upgrade_no_options(self, ): result = self.run_command(['hw', 'upgrade', '100']) From fb00f9dd62314fcd6999b9b3e6b7598e6ba5a33c Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 22 Mar 2021 15:45:29 -0400 Subject: [PATCH 0589/1582] Add authorize block, file and portable storage to a vs. --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/virt/authorize_storage.py | 41 ++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Account.py | 19 +++++++++ SoftLayer/fixtures/SoftLayer_Virtual_Guest.py | 6 +++ SoftLayer/managers/vs.py | 42 +++++++++++++++++++ docs/cli/vs.rst | 4 ++ tests/CLI/modules/vs/vs_tests.py | 35 ++++++++++++++++ tests/managers/vs/vs_tests.py | 16 +++++++ 8 files changed, 164 insertions(+) create mode 100644 SoftLayer/CLI/virt/authorize_storage.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..1e1a2db17 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -45,6 +45,7 @@ ('virtual:upgrade', 'SoftLayer.CLI.virt.upgrade:cli'), ('virtual:usage', 'SoftLayer.CLI.virt.usage:cli'), ('virtual:credentials', 'SoftLayer.CLI.virt.credentials:cli'), + ('virtual:authorize-storage', 'SoftLayer.CLI.virt.authorize_storage:cli'), ('virtual:capacity', 'SoftLayer.CLI.virt.capacity:cli'), ('virtual:placementgroup', 'SoftLayer.CLI.virt.placementgroup:cli'), ('virtual:migrate', 'SoftLayer.CLI.virt.migrate:cli'), diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py new file mode 100644 index 000000000..3929e863f --- /dev/null +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -0,0 +1,41 @@ +"""Authorize File, Block and Portable Storage to a Virtual Server""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers + + +@click.command() +@click.argument('identifier') +@click.option('--username-storage', '-u', type=click.STRING, + help="The storage username to be added to the virtual server") +@click.option('--portable-id', type=click.INT, + help="The portable storage id to be added to the virtual server") +@environment.pass_env +def cli(env, identifier, username_storage, portable_id): + """Authorize File, Block and Portable Storage to a Virtual Server. + """ + vs = SoftLayer.VSManager(env.client) + vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") + table.align['name'] = 'r' + table.align['value'] = 'l' + + if username_storage: + if not vs.authorize_storage(vs_id, username_storage): + raise exceptions.CLIAbort('Authorize Volume Failed') + env.fout('Successfully Volume: %s was Added.' % username_storage) + if portable_id: + portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') + portable_result = vs.attach_portable_storage(vs_id, portable_id) + + env.fout('Successfully Portable Storage: %i was Added.' % portable_id) + + table.add_row(['Id', portable_result['id']]) + table.add_row(['createDate', portable_result['createDate']]) + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 3c82b3da3..502c6b295 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1038,3 +1038,22 @@ "statusId": 2 } }] + +getNetworkStorage = [ + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2016-01-21T12:11:07-06:00", + "id": 1234567, + "nasType": "ISCSI", + "username": "SL01SEL301234-11", + }, + { + "accountId": 1111111, + "capacityGb": 20, + "createDate": "2015-04-29T07:55:55-06:00", + "id": 4917123, + "nasType": "NAS", + "username": "SL01SEV1234567_111" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py index ca72350c1..76d95dde0 100644 --- a/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py +++ b/SoftLayer/fixtures/SoftLayer_Virtual_Guest.py @@ -875,3 +875,9 @@ migrate = True migrateDedicatedHost = True +allowAccessToNetworkStorageList = True + +attachDiskImage = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index b247e8be5..c0eb91b9a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -1404,3 +1404,45 @@ def get_hardware_guests(self): object_filter = {"hardware": {"virtualHost": {"id": {"operation": "not null"}}}} mask = "mask[virtualHost[guests[powerState]]]" return self.client.call('SoftLayer_Account', 'getHardware', mask=mask, filter=object_filter) + + def authorize_storage(self, vs_id, username_storage): + """Authorize File or Block Storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param string username_storage: Storage username. + + :return: bool. + """ + _filter = {"networkStorage": {"username": {"operation": username_storage}}} + + storage_result = self.client.call('Account', 'getNetworkStorage', filter=_filter) + + if len(storage_result) == 0: + raise SoftLayerError("The Storage with username: %s was not found, please" + " enter a valid storage username" % username_storage) + + storage_template = [ + { + "id": storage_result[0]['id'], + "username": username_storage + } + ] + + result = self.client.call('SoftLayer_Virtual_Guest', 'allowAccessToNetworkStorageList', + storage_template, id=vs_id) + + return result + + def attach_portable_storage(self, vs_id, portable_id): + """Attach portal storage to a Virtual Server. + + :param int vs_id: Virtual server id. + :param int portable_id: Portal storage id. + + :return: SoftLayer_Provisioning_Version1_Transaction. + """ + disk_id = portable_id + result = self.client.call('SoftLayer_Virtual_Guest', 'attachDiskImage', + disk_id, id=vs_id) + + return result diff --git a/docs/cli/vs.rst b/docs/cli/vs.rst index 811e38c98..6227e0570 100644 --- a/docs/cli/vs.rst +++ b/docs/cli/vs.rst @@ -267,6 +267,10 @@ If no timezone is specified, IMS local time (CST) will be assumed, which might n :prog: virtual migrate :show-nested: +.. click:: SoftLayer.CLI.virt.authorize_storage:cli + :prog: virtual authorize-storage + :show-nested: + Manages the migration of virutal guests. Supports migrating virtual guests on Dedicated Hosts as well. Reserved Capacity diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 7fb23b97b..de3a310b9 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -892,3 +892,38 @@ def test_credentail(self): "username": "user", "password": "pass" }]) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_storage_vs_no_confirm(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vs', 'authorize-storage', '-u', '1234']) + + self.assertEqual(result.exit_code, 2) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_authorize_vs_empty(self, confirm_mock): + confirm_mock.return_value = True + storage_result = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + storage_result.return_value = [] + result = self.run_command(['vs', 'authorize-storage', '--username-storage=#', '1234']) + + self.assertEqual(str(result.exception), "The Storage with username: # was not found, " + "please enter a valid storage username") + + def test_authorize_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', '1234']) + self.assert_no_fail(result) + + def test_authorize_portable_storage_vs(self): + mock = self.set_mock('SoftLayer_Virtual_Guest', 'attachDiskImage') + mock.return_value = { + "createDate": "2021-03-22T13:15:31-06:00", + "id": 1234567 + } + result = self.run_command(['vs', 'authorize-storage', '--portable-id=12345', '1234']) + self.assert_no_fail(result) + + def test_authorize_volume_and_portable_storage_vs(self): + result = self.run_command(['vs', 'authorize-storage', '--username-storage=SL01SEL301234-11', + '--portable-id=12345', '1234']) + self.assert_no_fail(result) diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index c75e5e3b9..d5202bbe8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -1309,3 +1309,19 @@ def test_get_hardware_guests(self): result = self.vs.get_hardware_guests() self.assertEqual("NSX-T Manager", result[0]['virtualHost']['guests'][0]['hostname']) + + def test_authorize_storage(self): + options = self.vs.authorize_storage(1234, "SL01SEL301234-11") + + self.assertEqual(True, options) + + def test_authorize_storage_empty(self): + mock = self.set_mock('SoftLayer_Account', 'getNetworkStorage') + mock.return_value = [] + self.assertRaises(SoftLayer.exceptions.SoftLayerError, + self.vs.authorize_storage, + 1234, "#") + + def test_authorize_portable_storage(self): + options = self.vs.attach_portable_storage(1234, 1234567) + self.assertEqual(1234567, options['id']) From 75444c4ea9635ac40082c672115a4e55f6a610a1 Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:03:46 -0400 Subject: [PATCH 0590/1582] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index 3929e863f..d2080eb52 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -18,8 +18,7 @@ help="The portable storage id to be added to the virtual server") @environment.pass_env def cli(env, identifier, username_storage, portable_id): - """Authorize File, Block and Portable Storage to a Virtual Server. - """ + """Authorize File, Block and Portable Storage to a Virtual Server.""" vs = SoftLayer.VSManager(env.client) vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") From 4ff8abaae4508f74b19702199ed8f05ac9ac1e5a Mon Sep 17 00:00:00 2001 From: FernandoOjeda <39388146+FernandoOjeda@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:12:31 -0400 Subject: [PATCH 0591/1582] Fix tox analysis. --- SoftLayer/CLI/virt/authorize_storage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/virt/authorize_storage.py b/SoftLayer/CLI/virt/authorize_storage.py index d2080eb52..3228e8800 100644 --- a/SoftLayer/CLI/virt/authorize_storage.py +++ b/SoftLayer/CLI/virt/authorize_storage.py @@ -19,19 +19,19 @@ @environment.pass_env def cli(env, identifier, username_storage, portable_id): """Authorize File, Block and Portable Storage to a Virtual Server.""" - vs = SoftLayer.VSManager(env.client) - vs_id = helpers.resolve_id(vs.resolve_ids, identifier, 'vs') + virtual = SoftLayer.VSManager(env.client) + virtual_id = helpers.resolve_id(virtual.resolve_ids, identifier, 'virtual') table = formatting.KeyValueTable(['name', 'value'], title="Portable Storage Detail") table.align['name'] = 'r' table.align['value'] = 'l' if username_storage: - if not vs.authorize_storage(vs_id, username_storage): + if not virtual.authorize_storage(virtual_id, username_storage): raise exceptions.CLIAbort('Authorize Volume Failed') env.fout('Successfully Volume: %s was Added.' % username_storage) if portable_id: - portable_id = helpers.resolve_id(vs.resolve_ids, portable_id, 'storage') - portable_result = vs.attach_portable_storage(vs_id, portable_id) + portable_id = helpers.resolve_id(virtual.resolve_ids, portable_id, 'storage') + portable_result = virtual.attach_portable_storage(virtual_id, portable_id) env.fout('Successfully Portable Storage: %i was Added.' % portable_id) From 9f5abad79d7b5de168cc8a0c67bb439d47ff2384 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 22 Mar 2021 17:16:27 -0500 Subject: [PATCH 0592/1582] #1315 basic IAM authentication support, and slcli login function --- SoftLayer/API.py | 163 +++++++++++++++++++++++++++++++++- SoftLayer/CLI/config/login.py | 96 ++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/__init__.py | 1 + SoftLayer/auth.py | 29 ++++++ SoftLayer/config.py | 18 ++++ SoftLayer/consts.py | 1 + 7 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 430ed2d14..81e667817 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,16 +6,24 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import json +import logging +import requests import warnings from SoftLayer import auth as slauth from SoftLayer import config from SoftLayer import consts +from SoftLayer import exceptions from SoftLayer import transports + +LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT +CONFIG_FILE = consts.CONFIG_FILE + __all__ = [ 'create_client_from_env', 'Client', @@ -80,6 +88,8 @@ def create_client_from_env(username=None, 'Your Company' """ + if config_file is None: + config_file = CONFIG_FILE settings = config.get_client_settings(username=username, api_key=api_key, endpoint_url=endpoint_url, @@ -127,7 +137,7 @@ def create_client_from_env(username=None, settings.get('api_key'), ) - return BaseClient(auth=auth, transport=transport) + return BaseClient(auth=auth, transport=transport, config_file=config_file) def Client(**kwargs): @@ -150,9 +160,35 @@ class BaseClient(object): _prefix = "SoftLayer_" - def __init__(self, auth=None, transport=None): + def __init__(self, auth=None, transport=None, config_file=None): + if config_file is None: + config_file = CONFIG_FILE self.auth = auth - self.transport = transport + self.config_file = config_file + self.settings = config.get_config(self.config_file) + + if transport is None: + url = self.settings['softlayer'].get('endpoint_url') + if url is not None and '/rest' in url: + # If this looks like a rest endpoint, use the rest transport + transport = transports.RestTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + else: + # Default the transport to use XMLRPC + transport = transports.XmlRpcTransport( + endpoint_url=url, + proxy=self.settings['softlayer'].get('proxy'), + timeout=self.settings['softlayer'].getint('timeout'), + user_agent=consts.USER_AGENT, + verify=self.settings['softlayer'].getboolean('verify'), + ) + + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -321,6 +357,127 @@ def __repr__(self): def __len__(self): return 0 +class IAMClient(BaseClient): + """IBM ID Client for using IAM authentication + + :param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase + :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) + """ + + + def authenticate_with_password(self, username, password): + """Performs IBM IAM Username/Password Authentication + + :param string username: your IBMid username + :param string password: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'password', + 'password': password, + 'response_type': 'cloud_iam', + 'username': username + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + + config.write_config(self.settings) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens + + def authenticate_with_iam_token(self, a_token, r_token): + """Authenticates to the SL API with an IAM Token + + :param string a_token: Access token + :param string r_token: Refresh Token, to be used if Access token is expired. + """ + self.auth = slauth.BearerAuthentication('', a_token) + user = None + try: + user = self.call('Account', 'getCurrentUser') + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + # self.refresh_iam_token(r_token) + else: + raise ex + return user + + def refresh_iam_token(self, r_token): + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'refresh_token', + 'refresh_token': r_token, + 'response_type': 'cloud_iam' + } + + config = self.settings.get('softlayer') + if config.get('account', False): + data['account'] = account + if config.get('ims_account', False): + data['ims_account'] = ims_account + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + response.raise_for_status() + + LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + config.write_config(self.settings) + return tokens + + + + def call(self, service, method, *args, **kwargs): + """Handles refreshing IAM tokens in case of a HTTP 401 error""" + try: + return super().call(service, method, *args, **kwargs) + except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: + LOGGER.warning("Token has expired, trying to refresh.") + self.refresh_iam_token(r_token) + return super().call(service, method, *args, **kwargs) + else: + raise ex + + + def __repr__(self): + return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) + class Service(object): """A SoftLayer Service. diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py new file mode 100644 index 000000000..546ce6fdb --- /dev/null +++ b/SoftLayer/CLI/config/login.py @@ -0,0 +1,96 @@ +"""Gets a temporary token for a user""" +# :license: MIT, see LICENSE for more details. +import configparser +import os.path + + +import click +import json +import requests + +import SoftLayer +from SoftLayer import config +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + + email = env.input("Email:") + password = env.getpass("Password:") + + account_id = '' + ims_id = '' + print("ENV CONFIG FILE IS {}".format(env.config_file)) + sl_config = config.get_config(env.config_file) + tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) + print(user) + # tokens = client.authenticate_with_password(email, password) + + # tokens = login(email, password) + # print(tokens) + + + + accounts = get_accounts(tokens['access_token']) + print(accounts) + + # if accounts.get('total_results', 0) == 1: + # selected = accounts['resources'][0] + # account_id = utils.lookup(selected, 'metadata', 'guid') + # ims_id = None + # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): + # if links.get('origin') == "IMS": + # ims_id = links.get('id') + + # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) + # print(tokens) + + # print("Saving Tokens...") + + + for key in sl_config['softlayer']: + print("{} = {} ".format(key, sl_config['softlayer'][key])) + + # sl_config['softlayer']['access_token'] = tokens['access_token'] + # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] + # sl_config['softlayer']['ims_account'] = ims_id + # sl_config['softlayer']['account_id'] = account_id + # config.write_config(sl_config, env.config_file) + # print(sl_config) + + # print("Email: {}, Password: {}".format(email, password)) + + print("Checking for an API key") + + user = client.call('SoftLayer_Account', 'getCurrentUser') + print(user) + + + +def get_accounts(a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + return json.loads(response.text) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index c223bfae2..070f32c42 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,6 +70,7 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), + ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), diff --git a/SoftLayer/__init__.py b/SoftLayer/__init__.py index 9e14ea38e..30dd65774 100644 --- a/SoftLayer/__init__.py +++ b/SoftLayer/__init__.py @@ -31,6 +31,7 @@ __copyright__ = 'Copyright 2016 SoftLayer Technologies, Inc.' __all__ = [ # noqa: F405 'BaseClient', + 'IAMClient', 'create_client_from_env', 'Client', 'BasicAuthentication', diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index 4046937e6..c2d22168e 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,6 +7,8 @@ """ # pylint: disable=no-self-use +from SoftLayer import config + __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -109,3 +111,30 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + +class BearerAuthentication(AuthenticationBase): + """Bearer Token authentication class. + + :param username str: a user's username, not really needed but all the others use it. + :param api_key str: a user's IAM Token + """ + + def __init__(self, username, token, r_token=None): + """For using IBM IAM authentication + + :param username str: Not really needed, will be set to their current username though for logging + :param token str: the IAM Token + :param r_token str: The refresh Token, optional + """ + self.username = username + self.api_key = token + self.r_token = r_token + + def get_request(self, request): + """Sets token-based auth headers.""" + request.transport_headers['Authorization'] = 'Bearer {}'.format(self.api_key) + request.transport_user = self.username + return request + + def __repr__(self): + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file diff --git a/SoftLayer/config.py b/SoftLayer/config.py index d008893f0..3caebde0f 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -6,9 +6,11 @@ :license: MIT, see LICENSE for more details. """ import configparser +import logging import os import os.path +LOGGER = logging.getLogger(__name__) def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -91,3 +93,19 @@ def get_client_settings(**kwargs): all_settings = settings return all_settings + + +def get_config(config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config = configparser.ConfigParser() + config.read(os.path.expanduser(config_file)) + return config + +def write_config(configuration, config_file=None): + if config_file is None: + config_file = '~/.softlayer' + config_file = os.path.expanduser(config_file) + LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) + with open(config_file, 'w') as file: + configuration.write(file) \ No newline at end of file diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index b10b164d0..71c52be75 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -11,3 +11,4 @@ API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' API_PRIVATE_ENDPOINT_REST = 'https://api.service.softlayer.com/rest/v3.1/' USER_AGENT = "softlayer-python/%s" % VERSION +CONFIG_FILE = "~/.softlayer" From a51e9cf917458dc8a77ddccaeadb320abb75010d Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 23 Mar 2021 15:04:28 -0400 Subject: [PATCH 0593/1582] Refactor hw detail prices. --- SoftLayer/CLI/hardware/detail.py | 10 ++++++---- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..95410fc49 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -78,11 +78,13 @@ def cli(env, identifier, passwords, price): if price: total_price = utils.lookup(result, 'billingItem', 'nextInvoiceTotalRecurringAmount') or 0 - price_table = formatting.Table(['Item', 'Recurring Price']) - price_table.add_row(['Total', total_price]) + price_table = formatting.Table(['Item', 'CategoryCode', 'Recurring Price']) + price_table.align['Item'] = 'l' - for item in utils.lookup(result, 'billingItem', 'children') or []: - price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) + price_table.add_row(['Total', '-', total_price]) + + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: + price_table.add_row([item['description'], item['categoryCode'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..dd1dedf68 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -260,7 +260,7 @@ def get_hardware(self, hardware_id, **kwargs): passwords[username,password]],''' 'billingItem[' 'id,nextInvoiceTotalRecurringAmount,' - 'children[nextInvoiceTotalRecurringAmount],' + 'nextInvoiceChildren[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' 'hourlyBillingFlag,' From 1db96f6f26c72223bbe10a7eff60e065cc969db1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 24 Mar 2021 17:33:57 -0400 Subject: [PATCH 0594/1582] add billing and lastTransaction on hardware detail --- SoftLayer/CLI/hardware/detail.py | 2 ++ SoftLayer/managers/hardware.py | 1 + 2 files changed, 3 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..f87f5206d 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,6 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) + table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..9045d9bda 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,6 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' + 'lastTransaction[transactionGroup[name]],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 20630befee1efbaa74f520195fd22235f3f423f8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 24 Mar 2021 17:23:19 -0500 Subject: [PATCH 0595/1582] #1315 moved the IBMID login stuff to config setup instead of its own command --- SoftLayer/API.py | 105 ++++++++++----- SoftLayer/CLI/config/login.py | 96 -------------- SoftLayer/CLI/config/setup.py | 232 +++++++++++++++++++++++++++++----- SoftLayer/CLI/routes.py | 1 - 4 files changed, 275 insertions(+), 159 deletions(-) delete mode 100644 SoftLayer/CLI/config/login.py diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 81e667817..2ef237d6c 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -10,7 +10,7 @@ import logging import requests import warnings - +import time from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +18,7 @@ from SoftLayer import exceptions from SoftLayer import transports - +from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -402,29 +402,63 @@ def authenticate_with_password(self, username, password): self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + + return tokens + + def authenticate_with_passcode(self, passcode): + """Performs IBM IAM SSO Authentication + + :param string passcode: your IBMid password + """ + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': consts.USER_AGENT, + 'Accept': 'application/json' + } + data = { + 'grant_type': 'urn:ibm:params:oauth:grant-type:passcode', + 'passcode': passcode, + 'response_type': 'cloud_iam' + } + + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: {}".format(response.text)) + + response.raise_for_status() + + tokens = json.loads(response.text) + self.settings['softlayer']['access_token'] = tokens['access_token'] + self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) + return tokens - def authenticate_with_iam_token(self, a_token, r_token): + def authenticate_with_iam_token(self, a_token, r_token=None): """Authenticates to the SL API with an IAM Token :param string a_token: Access token :param string r_token: Refresh Token, to be used if Access token is expired. """ - self.auth = slauth.BearerAuthentication('', a_token) - user = None - try: - user = self.call('Account', 'getCurrentUser') - except exceptions.SoftLayerAPIError as ex: - if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - # self.refresh_iam_token(r_token) - else: - raise ex - return user + self.auth = slauth.BearerAuthentication('', a_token, r_token) - def refresh_iam_token(self, r_token): + def refresh_iam_token(self, r_token, account_id=None, ims_account=None): + """Refreshes the IAM Token, will default to values in the config file""" iam_client = requests.Session() headers = { @@ -438,11 +472,15 @@ def refresh_iam_token(self, r_token): 'response_type': 'cloud_iam' } - config = self.settings.get('softlayer') - if config.get('account', False): - data['account'] = account - if config.get('ims_account', False): - data['ims_account'] = ims_account + sl_config = self.settings['softlayer'] + + if account_id is None and sl_config.get('account_id', False): + account_id = sl_config.get('account_id') + if ims_account is None and sl_config.get('ims_account', False): + ims_account = sl_config.get('ims_account') + + data['account'] = account_id + data['ims_account'] = ims_account response = iam_client.request( 'POST', @@ -451,30 +489,37 @@ def refresh_iam_token(self, r_token): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) + response.raise_for_status() - - LOGGER.warning("Successfully refreshed Tokens, saving to config") + tokens = json.loads(response.text) + a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) + r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) + LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - config.write_config(self.settings) + config.write_config(self.settings, self.config_file) + self.auth = slauth.BearerAuthentication('', tokens['access_token']) return tokens - - def call(self, service, method, *args, **kwargs): """Handles refreshing IAM tokens in case of a HTTP 401 error""" try: return super().call(service, method, *args, **kwargs) except exceptions.SoftLayerAPIError as ex: + if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh.") - self.refresh_iam_token(r_token) - return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) + # self.refresh_iam_token(r_token) + # return super().call(service, method, *args, **kwargs) + return ex else: raise ex - def __repr__(self): return "IAMClient(transport=%r, auth=%r)" % (self.transport, self.auth) diff --git a/SoftLayer/CLI/config/login.py b/SoftLayer/CLI/config/login.py deleted file mode 100644 index 546ce6fdb..000000000 --- a/SoftLayer/CLI/config/login.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Gets a temporary token for a user""" -# :license: MIT, see LICENSE for more details. -import configparser -import os.path - - -import click -import json -import requests - -import SoftLayer -from SoftLayer import config -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.CLI import formatting -from SoftLayer.consts import USER_AGENT -from SoftLayer import utils - - -@click.command() -@environment.pass_env -def cli(env): - - email = env.input("Email:") - password = env.getpass("Password:") - - account_id = '' - ims_id = '' - print("ENV CONFIG FILE IS {}".format(env.config_file)) - sl_config = config.get_config(env.config_file) - tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} - client = SoftLayer.API.IAMClient(config_file=env.config_file) - user = client.authenticate_with_iam_token(tokens['access_token'], tokens['refresh_token']) - print(user) - # tokens = client.authenticate_with_password(email, password) - - # tokens = login(email, password) - # print(tokens) - - - - accounts = get_accounts(tokens['access_token']) - print(accounts) - - # if accounts.get('total_results', 0) == 1: - # selected = accounts['resources'][0] - # account_id = utils.lookup(selected, 'metadata', 'guid') - # ims_id = None - # for links in utils.lookup(selected, 'metadata', 'linked_accounts'): - # if links.get('origin') == "IMS": - # ims_id = links.get('id') - - # print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) - # tokens = refresh_token(tokens['refresh_token'], account_id, ims_id) - # print(tokens) - - # print("Saving Tokens...") - - - for key in sl_config['softlayer']: - print("{} = {} ".format(key, sl_config['softlayer'][key])) - - # sl_config['softlayer']['access_token'] = tokens['access_token'] - # sl_config['softlayer']['refresh_token'] = tokens['refresh_token'] - # sl_config['softlayer']['ims_account'] = ims_id - # sl_config['softlayer']['account_id'] = account_id - # config.write_config(sl_config, env.config_file) - # print(sl_config) - - # print("Email: {}, Password: {}".format(email, password)) - - print("Checking for an API key") - - user = client.call('SoftLayer_Account', 'getCurrentUser') - print(user) - - - -def get_accounts(a_token): - """Gets account list from accounts.cloud.ibm.com/v1/accounts""" - iam_client = requests.Session() - - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': USER_AGENT, - 'Accept': 'application/json' - } - headers['Authorization'] = 'Bearer {}'.format(a_token) - response = iam_client.request( - 'GET', - 'https://accounts.cloud.ibm.com/v1/accounts', - headers=headers - ) - - response.raise_for_status() - return json.loads(response.text) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 7f1833a4a..26d7805ee 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,7 +1,10 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. import configparser +import json +import requests import os.path +import webbrowser import click @@ -10,7 +13,11 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting +from SoftLayer import config as base_config +from SoftLayer.consts import USER_AGENT +from SoftLayer import utils +from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -27,27 +34,68 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur if 'invalid api token' not in ex.faultString.lower(): raise else: - # Try to use a client with username/password - client.authenticate_with_password(username, secret) + if isinstance(client, SoftLayer.API.IAMClient): + client.authenticate_with_iam_token(secret) + else: + # Try to use a client with username/password + client.authenticate_with_password(username, secret) - user_record = client['Account'].getCurrentUser(mask='id, apiAuthenticationKeys') + user_record = client.call('Account', 'getCurrentUser', mask='id, apiAuthenticationKeys') api_keys = user_record['apiAuthenticationKeys'] if len(api_keys) == 0: - return client['User_Customer'].addApiAuthenticationKey(id=user_record['id']) + return client.call('User_Customer', 'addApiAuthenticationKey', id=user_record['id']) return api_keys[0]['authenticationKey'] @click.command() +@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), + help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env -def cli(env): +def cli(env, auth): """Setup the ~/.softlayer file with username and apikey. - Set the username to 'apikey' for cloud.ibm.com accounts. + [Auth Types] + + ibmid: Requires your cloud.ibm.com username and password, and generates a classic infrastructure API key. + + cloud_key: A 32 character API key. Username will be 'apikey' + + classic_key: A 64 character API key used in the Softlayer/Classic Infrastructure systems. + + sso: For users with @ibm.com email addresses. """ + username = None + api_key = None + + timeout = 0 + defaults = config.get_settings_from_client(env.client) + endpoint_url = defaults.get('endpoint_url', 'public') + # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + # Get ths username and API key + if auth == 'ibmid': + print("Logging in with IBMid") + username, api_key = ibmid_login(env) + + elif auth == 'cloud_key': + username = 'apikey' + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) - username, secret, endpoint_url, timeout = get_user_input(env) - new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) - api_key = get_api_key(new_client, username, secret) + elif auth =='sso': + print("Using SSO for login") + username, api_key = sso_login(env) + else: + print("Using a Classic Infrastructure API key") + + username = env.input('Classic Infrastructue Username', default=defaults['username']) + secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + + new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) + api_key = get_api_key(new_client, username, secret) + + # Ask for timeout + timeout = env.input('Timeout', default=defaults['timeout'] or 0) path = '~/.softlayer' if env.config_file: @@ -59,8 +107,7 @@ def cli(env): 'endpoint_url': endpoint_url, 'timeout': timeout}))) - if not formatting.confirm('Are you sure you want to write settings ' - 'to "%s"?' % config_path, default=True): + if not formatting.confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): raise exceptions.CLIAbort('Aborted.') # Persist the config file. Read the target config file in before @@ -77,10 +124,7 @@ def cli(env): parsed_config.set('softlayer', 'endpoint_url', endpoint_url) parsed_config.set('softlayer', 'timeout', timeout) - config_fd = os.fdopen(os.open(config_path, - (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), - 0o600), - 'w') + config_fd = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT | os.O_TRUNC), 0o600), 'w') try: parsed_config.write(config_fd) finally: @@ -89,21 +133,10 @@ def cli(env): env.fout("Configuration Updated Successfully") -def get_user_input(env): - """Ask for username, secret (api_key or password) and endpoint_url.""" +def get_endpoint_url(env, endpoint='public'): + """Gets the Endpoint to use.""" - defaults = config.get_settings_from_client(env.client) - - # Ask for username - username = env.input('Username', default=defaults['username']) - - # Ask for 'secret' which can be api_key or their password - secret = env.getpass('API Key or Password', default=defaults['api_key']) - - # Ask for which endpoint they want to use - endpoint = defaults.get('endpoint_url', 'public') - endpoint_type = env.input( - 'Endpoint (public|private|custom)', default=endpoint) + endpoint_type = env.input('Endpoint (public|private|custom)', default=endpoint) endpoint_type = endpoint_type.lower() if endpoint_type == 'public': @@ -116,7 +149,142 @@ def get_user_input(env): else: endpoint_url = endpoint_type - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) - return username, secret, endpoint_url, timeout + return endpoint_url + +def ibmid_login(env): + """Uses an IBMid and Password to get an access token, and that access token to get an API key""" + email = env.input("Email").strip() + password = env.getpass("Password").strip() + + account_id = '' + ims_id = '' + sl_config = base_config.get_config(env.config_file) + # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_password(email, password) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + + return user.get('username'), api_key + + +def get_accounts(env, a_token): + """Gets account list from accounts.cloud.ibm.com/v1/accounts""" + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + headers['Authorization'] = 'Bearer {}'.format(a_token) + response = iam_client.request( + 'GET', + 'https://accounts.cloud.ibm.com/v1/accounts', + headers=headers + ) + + response.raise_for_status() + + accounts = json.loads(response.text) + selected = None + ims_id = None + if accounts.get('total_results', 0) == 1: + selected = accounts['resources'][0] + else: + env.fout("Select an Account...") + counter = 1 + for selected in accounts.get('resources', []): + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + if ims_id is None: + ims_id = "Unlinked" + env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) + counter = counter + 1 + ims_id = None # Reset ims_id to avoid any mix-match or something. + choice = click.prompt('Enter a number', type=int) + # Test to make sure choice is not out of bounds... + selected = accounts['resources'][choice - 1] + + + account_id = utils.lookup(selected, 'metadata', 'guid') + links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] + for link in links: + if link.get('origin') == "IMS": + ims_id = link.get('id') + + print("Using account {}".format(utils.lookup(selected, 'entity', 'name'))) + return {"account_id": account_id, "ims_id": ims_id} + + +def get_sso_url(): + """Gets the URL for using SSO Tokens""" + + iam_client = requests.Session() + + headers = { + 'Content-Type': 'application/json', + 'User-Agent': USER_AGENT, + 'Accept': 'application/json' + } + response = iam_client.request( + 'GET', + 'https://iam.cloud.ibm.com/identity/.well-known/openid-configuration', + headers=headers + ) + + response.raise_for_status() + data = json.loads(response.text) + return data.get('passcode_endpoint') + +def sso_login(env): + """Uses a SSO token to get a SL apikey""" + account_id = '' + ims_id = '' + + passcode_url = get_sso_url() + env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) + open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') + if open_browser.lower() == 'y': + webbrowser.open(passcode_url) + passcode = env.input("One-time code") + client = SoftLayer.API.IAMClient(config_file=env.config_file) + + # STEP 1: Get the base IAM Token with a username/password + tokens = client.authenticate_with_passcode(passcode) + + # STEP 2: Figure out which account we want to use + account = get_accounts(env, tokens['access_token']) + + # STEP 3: Refresh Token, using a specific account this time. + tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) + + # STEP 4: Get or create the Classic Infrastructure API key + # client.authenticate_with_iam_token(tokens['access_token']) + user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") + + if len(user.get('apiAuthenticationKeys', [])) == 0: + env.fout("Creating a Classic Infrastrucutre API key for {}".format(user['username'])) + api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) + else: + api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] + return user.get('username'), api_key \ No newline at end of file diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 070f32c42..c223bfae2 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -70,7 +70,6 @@ ('config:setup', 'SoftLayer.CLI.config.setup:cli'), ('config:show', 'SoftLayer.CLI.config.show:cli'), ('setup', 'SoftLayer.CLI.config.setup:cli'), - ('login', 'SoftLayer.CLI.config.login:cli'), ('dns', 'SoftLayer.CLI.dns'), ('dns:import', 'SoftLayer.CLI.dns.zone_import:cli'), From 7ae7588bd5ee098de67be601f35b3a74439acb1c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 25 Mar 2021 17:22:08 -0500 Subject: [PATCH 0596/1582] Tox fixes --- SoftLayer/API.py | 35 ++++++++--------- SoftLayer/CLI/config/setup.py | 47 ++++++++--------------- SoftLayer/auth.py | 7 ++-- SoftLayer/config.py | 7 +++- tests/CLI/modules/config_tests.py | 64 +++++++++++-------------------- 5 files changed, 64 insertions(+), 96 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 2ef237d6c..5d15681ce 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -6,11 +6,13 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=invalid-name +import time +import warnings + import json import logging import requests -import warnings -import time + from SoftLayer import auth as slauth from SoftLayer import config @@ -18,7 +20,6 @@ from SoftLayer import exceptions from SoftLayer import transports -from pprint import pprint as pp LOGGER = logging.getLogger(__name__) API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT @@ -188,7 +189,7 @@ def __init__(self, auth=None, transport=None, config_file=None): verify=self.settings['softlayer'].getboolean('verify'), ) - self.transport = transport + self.transport = transport def authenticate_with_password(self, username, password, security_question_id=None, @@ -357,6 +358,7 @@ def __repr__(self): def __len__(self): return 0 + class IAMClient(BaseClient): """IBM ID Client for using IAM authentication @@ -364,8 +366,7 @@ class IAMClient(BaseClient): :param transport: An object that's callable with this signature: transport(SoftLayer.transports.Request) """ - - def authenticate_with_password(self, username, password): + def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): """Performs IBM IAM Username/Password Authentication :param string username: your IBMid username @@ -394,14 +395,14 @@ def authenticate_with_password(self, username, password): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] - + config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -434,7 +435,7 @@ def authenticate_with_passcode(self, passcode): auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) if response.status_code != 200: - LOGGER.error("Unable to login: {}".format(response.text)) + LOGGER.error("Unable to login: %s", response.text) response.raise_for_status() @@ -443,7 +444,7 @@ def authenticate_with_passcode(self, passcode): self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Tokens retrieved, expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) config.write_config(self.settings, self.config_file) self.auth = slauth.BearerAuthentication('', tokens['access_token'], tokens['refresh_token']) @@ -489,16 +490,16 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): headers=headers, auth=requests.auth.HTTPBasicAuth('bx', 'bx') ) - + if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. {}".format(response.text)) - + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() - + tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) - LOGGER.warning("Successfully refreshed Tokens. Expires at {}, Refresh expires at {}".format(a_expire, r_expire)) + LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -513,9 +514,7 @@ def call(self, service, method, *args, **kwargs): except exceptions.SoftLayerAPIError as ex: if ex.faultCode == 401: - LOGGER.warning("Token has expired, trying to refresh. {}".format(ex.faultString)) - # self.refresh_iam_token(r_token) - # return super().call(service, method, *args, **kwargs) + LOGGER.warning("Token has expired, trying to refresh. %s", ex.faultString) return ex else: raise ex diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 26d7805ee..476ab64b3 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -1,10 +1,11 @@ """Setup CLI configuration.""" # :license: MIT, see LICENSE for more details. +import webbrowser + import configparser import json -import requests import os.path -import webbrowser +import requests import click @@ -13,11 +14,9 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer import config as base_config from SoftLayer.consts import USER_AGENT from SoftLayer import utils -from pprint import pprint as pp def get_api_key(client, username, secret): # pylint: disable=inconsistent-return-statements """Attempts API-Key and password auth to get an API key. @@ -66,31 +65,26 @@ def cli(env, auth): """ username = None api_key = None - + timeout = 0 defaults = config.get_settings_from_client(env.client) - endpoint_url = defaults.get('endpoint_url', 'public') - # endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) + endpoint_url = get_endpoint_url(env, defaults.get('endpoint_url', 'public')) # Get ths username and API key if auth == 'ibmid': - print("Logging in with IBMid") username, api_key = ibmid_login(env) - + elif auth == 'cloud_key': username = 'apikey' - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - elif auth =='sso': - print("Using SSO for login") + elif auth == 'sso': username, api_key = sso_login(env) - else: - print("Using a Classic Infrastructure API key") + else: username = env.input('Classic Infrastructue Username', default=defaults['username']) - secret = env.input('Classic Infrastructue API Key', default=defaults['api_key']) - + secret = env.getpass('Classic Infrastructue API Key', default=defaults['api_key']) new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) @@ -148,32 +142,26 @@ def get_endpoint_url(env, endpoint='public'): endpoint_url = env.input('Endpoint URL', default=endpoint) else: endpoint_url = endpoint_type - - return endpoint_url + def ibmid_login(env): """Uses an IBMid and Password to get an access token, and that access token to get an API key""" email = env.input("Email").strip() password = env.getpass("Password").strip() - account_id = '' - ims_id = '' - sl_config = base_config.get_config(env.config_file) - # tokens = {'access_token': sl_config['softlayer']['access_token'], 'refresh_token': sl_config['softlayer']['refresh_token']} client = SoftLayer.API.IAMClient(config_file=env.config_file) - + # STEP 1: Get the base IAM Token with a username/password tokens = client.authenticate_with_password(email, password) # STEP 2: Figure out which account we want to use account = get_accounts(env, tokens['access_token']) - + # STEP 3: Refresh Token, using a specific account this time. tokens = client.refresh_iam_token(tokens['refresh_token'], account['account_id'], account['ims_id']) # STEP 4: Get or create the Classic Infrastructure API key - # client.authenticate_with_iam_token(tokens['access_token']) user = client.call('SoftLayer_Account', 'getCurrentUser', mask="mask[id,username,apiAuthenticationKeys]") if len(user.get('apiAuthenticationKeys', [])) == 0: @@ -220,12 +208,11 @@ def get_accounts(env, a_token): ims_id = "Unlinked" env.fout("{}: {} ({})".format(counter, utils.lookup(selected, 'entity', 'name'), ims_id)) counter = counter + 1 - ims_id = None # Reset ims_id to avoid any mix-match or something. + ims_id = None # Reset ims_id to avoid any mix-match or something. choice = click.prompt('Enter a number', type=int) # Test to make sure choice is not out of bounds... selected = accounts['resources'][choice - 1] - account_id = utils.lookup(selected, 'metadata', 'guid') links = utils.lookup(selected, 'metadata', 'linked_accounts') or [] for link in links: @@ -256,11 +243,9 @@ def get_sso_url(): data = json.loads(response.text) return data.get('passcode_endpoint') + def sso_login(env): """Uses a SSO token to get a SL apikey""" - account_id = '' - ims_id = '' - passcode_url = get_sso_url() env.fout("Get a one-time code from {} to proceed.".format(passcode_url)) open_browser = env.input("Open the URL in the default browser? [Y/n]", default='Y') @@ -287,4 +272,4 @@ def sso_login(env): api_key = client.call('User_Customer', 'addApiAuthenticationKey', id=user['id']) else: api_key = user['apiAuthenticationKeys'][0]['authenticationKey'] - return user.get('username'), api_key \ No newline at end of file + return user.get('username'), api_key diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py index c2d22168e..18e0ebe96 100644 --- a/SoftLayer/auth.py +++ b/SoftLayer/auth.py @@ -7,8 +7,6 @@ """ # pylint: disable=no-self-use -from SoftLayer import config - __all__ = [ 'BasicAuthentication', 'TokenAuthentication', @@ -112,6 +110,7 @@ def get_request(self, request): def __repr__(self): return "BasicHTTPAuthentication(username=%r)" % self.username + class BearerAuthentication(AuthenticationBase): """Bearer Token authentication class. @@ -121,7 +120,7 @@ class BearerAuthentication(AuthenticationBase): def __init__(self, username, token, r_token=None): """For using IBM IAM authentication - + :param username str: Not really needed, will be set to their current username though for logging :param token str: the IAM Token :param r_token str: The refresh Token, optional @@ -137,4 +136,4 @@ def get_request(self, request): return request def __repr__(self): - return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) \ No newline at end of file + return "BearerAuthentication(username={}, token={})".format(self.username, self.api_key) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 3caebde0f..2f48aa221 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -12,6 +12,7 @@ LOGGER = logging.getLogger(__name__) + def get_client_settings_args(**kwargs): """Retrieve client settings from user-supplied arguments. @@ -96,16 +97,18 @@ def get_client_settings(**kwargs): def get_config(config_file=None): + """Returns a parsed config object""" if config_file is None: config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) return config + def write_config(configuration, config_file=None): + """Writes a configuration to config_file""" if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - LOGGER.warning("Updating config file {} with new access tokens".format(config_file)) with open(config_file, 'w') as file: - configuration.write(file) \ No newline at end of file + configuration.write(file) diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 33c82520a..fca30af1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -22,9 +22,7 @@ class TestHelpShow(testing.TestCase): def set_up(self): - transport = transports.XmlRpcTransport( - endpoint_url='http://endpoint-url', - ) + transport = transports.XmlRpcTransport(endpoint_url='http://endpoint-url',) self.env.client = SoftLayer.BaseClient( transport=transport, auth=auth.BasicAuthentication('username', 'api-key')) @@ -50,6 +48,7 @@ def set_up(self): # used. transport = testing.MockableTransport(SoftLayer.FixtureTransport()) self.env.client = SoftLayer.BaseClient(transport=transport) + self.config_file = "./test_config_file" @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -62,7 +61,7 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = True getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + mocked_input.side_effect = ['public', 'user', 0] result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) @@ -84,55 +83,38 @@ def test_setup_cancel(self, mocked_input, getpass, confirm_mock, client): with tempfile.NamedTemporaryFile() as config_file: confirm_mock.return_value = False getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] - - result = self.run_command(['--config=%s' % config_file.name, - 'config', 'setup']) + mocked_input.side_effect = ['public', 'user', 0] + result = self.run_command(['--config=%s' % config_file.name, 'config', 'setup']) self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_private(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'private', 0] - - username, secret, endpoint_url, timeout = ( - config.get_user_input(self.env)) - - self.assertEqual(username, 'user') - self.assertEqual(secret, 'A' * 64) - self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - self.assertEqual(timeout, 0) - - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') - @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_custom(self, mocked_input, getpass): - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'custom', 'custom-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - - self.assertEqual(endpoint_url, 'custom-endpoint') - @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') def test_github_1074(self, mocked_input, getpass): """Tests to make sure directly using an endpoint works""" - getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'test-endpoint', 0] - - _, _, endpoint_url, _ = config.get_user_input(self.env) - + mocked_input.side_effect = ['test-endpoint'] + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @mock.patch('SoftLayer.CLI.environment.Environment.input') - def test_get_user_input_default(self, mocked_input, getpass): - self.env.getpass.return_value = 'A' * 64 - mocked_input.side_effect = ['user', 'public', 0] + def test_get_endpoint(self, mocked_input, getpass): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['private', 'custom', 'test.com', 'public', 'test-endpoint'] + + # private + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT) - _, _, endpoint_url, _ = config.get_user_input(self.env) + # custom - test.com + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test.com') + # public + endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, consts.API_PUBLIC_ENDPOINT) + + # test-endpoint + endpoint_url = config.get_endpoint_url(self.env) + self.assertEqual(endpoint_url, 'test-endpoint') From d7b2d5990f004b7355ca759f34bafa30c56debbd Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:04:20 -0400 Subject: [PATCH 0597/1582] #1450 add slcli order quote-save tests --- tests/CLI/modules/order_tests.py | 5 +++++ tests/managers/ordering_tests.py | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py index f09b5aaea..24495d0ff 100644 --- a/tests/CLI/modules/order_tests.py +++ b/tests/CLI/modules/order_tests.py @@ -401,6 +401,11 @@ def test_quote_detail(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order_Quote', 'getObject', identifier='12345') + def test_quote_save(self): + result = self.run_command(['order', 'quote-save', '12345']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier='12345') + def test_quote_list(self): result = self.run_command(['order', 'quote-list']) self.assert_no_fail(result) diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index f18d801a9..5f88d59d5 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -104,6 +104,13 @@ def test_get_quote_details(self): quote_fixture = quote_service.getObject(id=1234) self.assertEqual(quote, quote_fixture) + def test_save_quote(self): + saved_quote = self.ordering.save_quote(1234) + quote_service = self.ordering.client['Billing_Order_Quote'] + quote_fixture = quote_service.getObject(id=1234) + self.assertEqual(saved_quote, quote_fixture) + self.assert_called_with('SoftLayer_Billing_Order_Quote', 'saveQuote', identifier=1234) + def test_verify_quote(self): extras = { 'hardware': [{ From f46990ffe16f16c2be78862e4dd8d8d65cb36b6e Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:08 -0400 Subject: [PATCH 0598/1582] #1450 add slcli order quote-save feature --- SoftLayer/CLI/order/quote_save.py | 33 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../fixtures/SoftLayer_Billing_Order_Quote.py | 2 ++ SoftLayer/managers/ordering.py | 7 ++++ 4 files changed, 43 insertions(+) create mode 100644 SoftLayer/CLI/order/quote_save.py diff --git a/SoftLayer/CLI/order/quote_save.py b/SoftLayer/CLI/order/quote_save.py new file mode 100644 index 000000000..64b695ca0 --- /dev/null +++ b/SoftLayer/CLI/order/quote_save.py @@ -0,0 +1,33 @@ +"""Save a quote""" +# :license: MIT, see LICENSE for more details. +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import ordering +from SoftLayer.utils import clean_time + + +@click.command() +@click.argument('quote') +@environment.pass_env +def cli(env, quote): + """Save a quote""" + + manager = ordering.OrderingManager(env.client) + result = manager.save_quote(quote) + + table = formatting.Table([ + 'Id', 'Name', 'Created', 'Modified', 'Status' + ]) + table.align['Name'] = 'l' + + table.add_row([ + result.get('id'), + result.get('name'), + clean_time(result.get('createDate')), + clean_time(result.get('modifyDate')), + result.get('status'), + ]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 2e73aabf8..631d4b2be 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -227,6 +227,7 @@ ('order:place-quote', 'SoftLayer.CLI.order.place_quote:cli'), ('order:quote-list', 'SoftLayer.CLI.order.quote_list:cli'), ('order:quote-detail', 'SoftLayer.CLI.order.quote_detail:cli'), + ('order:quote-save', 'SoftLayer.CLI.order.quote_save:cli'), ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py index f1ca8c497..9e7340e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py +++ b/SoftLayer/fixtures/SoftLayer_Billing_Order_Quote.py @@ -102,3 +102,5 @@ ] } } + +saveQuote = getObject diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index 36bbb93ef..18513e1e4 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -169,6 +169,13 @@ def get_quote_details(self, quote_id): quote = self.client['Billing_Order_Quote'].getObject(id=quote_id, mask=mask) return quote + def save_quote(self, quote_id): + """Save a quote. + + :param quote_id: ID number of target quote + """ + return self.client['Billing_Order_Quote'].saveQuote(id=quote_id) + def get_order_container(self, quote_id): """Generate an order container from a quote object. From 68292e28474bd57df479cd398c8027f2f6a0f72b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 26 Mar 2021 16:06:34 -0400 Subject: [PATCH 0599/1582] #1450 add slcli order quote-save docs --- docs/cli/ordering.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cli/ordering.rst b/docs/cli/ordering.rst index 7405bdb0e..740438a69 100644 --- a/docs/cli/ordering.rst +++ b/docs/cli/ordering.rst @@ -131,6 +131,10 @@ Quotes :prog: order quote-detail :show-nested: +.. click:: SoftLayer.CLI.order.quote_save:cli + :prog: order quote-save + :show-nested: + .. click:: SoftLayer.CLI.order.place_quote:cli :prog: order place-quote :show-nested: From 5c3cd04515a21a2b522d7e776d1cb390155fe2fc Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 10:02:45 -0400 Subject: [PATCH 0600/1582] fix team code review comments --- SoftLayer/CLI/hardware/detail.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index f87f5206d..c5b1c2b9b 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,7 +61,8 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['LastTransaction', result['lastTransaction']['transactionGroup']['name']]) + table.add_row(['last_transaction', + utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) From 6a8a2d5f982e73d69d9ce2853e94177c8de90d29 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 29 Mar 2021 17:51:41 -0400 Subject: [PATCH 0601/1582] add the Hardware components on "slcli hardware detail" --- SoftLayer/CLI/hardware/detail.py | 12 ++++++++++++ SoftLayer/managers/hardware.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index d48a7e5ed..a1e31e023 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -71,6 +71,8 @@ def cli(env, identifier, passwords, price): bandwidth = hardware.get_bandwidth_allocation(hardware_id) bw_table = _bw_table(bandwidth) table.add_row(['Bandwidth', bw_table]) + system_table = _system_table(result['activeComponents']) + table.add_row(['System_data', system_table]) if result.get('notes'): table.add_row(['notes', result['notes']]) @@ -117,3 +119,13 @@ def _bw_table(bw_data): table.add_row([bw_type, bw_point['amountIn'], bw_point['amountOut'], allotment]) return table + + +def _system_table(system_data): + table = formatting.Table(['Type', 'name']) + for system in system_data: + table.add_row([utils.lookup(system, 'hardwareComponentModel', + 'hardwareGenericComponentModel', + 'hardwareComponentType', 'keyName'), + utils.lookup(system, 'hardwareComponentModel', 'longDescription')]) + return table diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c93be8d9a..e7e2d24db 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -239,6 +239,8 @@ def get_hardware(self, hardware_id, **kwargs): 'primaryIpAddress,' 'networkManagementIpAddress,' 'userData,' + 'activeComponents[id,hardwareComponentModel[' + 'hardwareGenericComponentModel[id,hardwareComponentType[keyName]]]],' 'datacenter,' '''networkComponents[id, status, speed, maxSpeed, name, ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress, From 8aa65113dd3b9470bfa524c57e5fe0bedba6ba54 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:57:48 -0500 Subject: [PATCH 0602/1582] fixed code review comments, a few more unit tests --- SoftLayer/API.py | 91 +++++++++++++++++++------------ SoftLayer/CLI/config/setup.py | 4 +- SoftLayer/exceptions.py | 15 +++++ tests/CLI/modules/config_tests.py | 56 +++++++++++++++++++ 4 files changed, 129 insertions(+), 37 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 5d15681ce..a21f9f654 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -175,7 +175,8 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.RestTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + # prevents an exception incase timeout is a float number. + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -184,7 +185,7 @@ def __init__(self, auth=None, transport=None, config_file=None): transport = transports.XmlRpcTransport( endpoint_url=url, proxy=self.settings['softlayer'].get('proxy'), - timeout=self.settings['softlayer'].getint('timeout'), + timeout=int(self.settings['softlayer'].getfloat('timeout')), user_agent=consts.USER_AGENT, verify=self.settings['softlayer'].getboolean('verify'), ) @@ -387,19 +388,25 @@ def authenticate_with_password(self, username, password, security_question_id=No 'username': username } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) - response.raise_for_status() + response.raise_for_status() + tokens = json.loads(response.text) + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -427,19 +434,26 @@ def authenticate_with_passcode(self, passcode): 'response_type': 'cloud_iam' } - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) - if response.status_code != 200: - LOGGER.error("Unable to login: %s", response.text) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + if response.status_code != 200: + LOGGER.error("Unable to login: %s", response.text) + + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) @@ -483,20 +497,27 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): data['account'] = account_id data['ims_account'] = ims_account - response = iam_client.request( - 'POST', - 'https://iam.cloud.ibm.com/identity/token', - data=data, - headers=headers, - auth=requests.auth.HTTPBasicAuth('bx', 'bx') - ) + try: + response = iam_client.request( + 'POST', + 'https://iam.cloud.ibm.com/identity/token', + data=data, + headers=headers, + auth=requests.auth.HTTPBasicAuth('bx', 'bx') + ) + + if response.status_code != 200: + LOGGER.warning("Unable to refresh IAM Token. %s", response.text) - if response.status_code != 200: - LOGGER.warning("Unable to refresh IAM Token. %s", response.text) + response.raise_for_status() + tokens = json.loads(response.text) - response.raise_for_status() + except requests.HTTPError as ex: + error = json.loads(response.text) + raise exceptions.IAMError(response.status_code, + error.get('errorMessage'), + 'https://iam.cloud.ibm.com/identity/token') - tokens = json.loads(response.text) a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) LOGGER.warning("Tokens retrieved, expires at %s, Refresh expires at %s", a_expire, r_expire) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index 476ab64b3..d5a8ee6f2 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -88,8 +88,8 @@ def cli(env, auth): new_client = SoftLayer.Client(username=username, api_key=secret, endpoint_url=endpoint_url, timeout=timeout) api_key = get_api_key(new_client, username, secret) - # Ask for timeout - timeout = env.input('Timeout', default=defaults['timeout'] or 0) + # Ask for timeout, convert to float, then to int + timeout = int(float(env.input('Timeout', default=defaults['timeout'] or 0))) path = '~/.softlayer' if env.config_file: diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index b3530aa8c..f444b8389 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,21 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" +class IAMError(SoftLayerError): + """Errors from iam.cloud.ibm.com""" + + def __init__(self, fault_code, fault_string, url=None): + SoftLayerError.__init__(self, fault_string) + self.faultCode = fault_code + self.faultString = fault_string + self.url = url + + def __repr__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + + def __str__(self): + return "{} ({}): {}".format(self.url, self.faultCode, self.faultString) + class SoftLayerAPIError(SoftLayerError): """SoftLayerAPIError is an exception raised during API errors. diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index fca30af1c..7783ac2a2 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json +import os import sys import tempfile @@ -50,6 +51,12 @@ def set_up(self): self.env.client = SoftLayer.BaseClient(transport=transport) self.config_file = "./test_config_file" + def tearDown(self): + # Windows doesn't let you write and read from temp files + # So use a real file instead. + if os.path.exists(self.config_file): + os.remove(self.config_file) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -74,6 +81,30 @@ def test_setup(self, mocked_input, getpass, confirm_mock, client): self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + @mock.patch('SoftLayer.Client') + @mock.patch('SoftLayer.CLI.formatting.confirm') + @mock.patch('SoftLayer.CLI.environment.Environment.getpass') + @mock.patch('SoftLayer.CLI.environment.Environment.input') + def test_setup_float_timeout(self, mocked_input, getpass, confirm_mock, client): + client.return_value = self.env.client + confirm_mock.return_value = True + getpass.return_value = 'A' * 64 + mocked_input.side_effect = ['public', 'user', 10.0] + + result = self.run_command(['--config=%s' % self.config_file, 'config', 'setup']) + + self.assert_no_fail(result) + self.assertIn('Configuration Updated Successfully', result.output) + + with open(self.config_file, 'r') as config_file: + contents = config_file.read() + self.assertIn('[softlayer]', contents) + self.assertIn('username = user', contents) + self.assertIn('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', contents) + self.assertIn('endpoint_url = %s' % consts.API_PUBLIC_ENDPOINT, contents) + self.assertNotIn('timeout = 10.0\n', contents) + self.assertIn('timeout = 10\n', contents) + @mock.patch('SoftLayer.Client') @mock.patch('SoftLayer.CLI.formatting.confirm') @mock.patch('SoftLayer.CLI.environment.Environment.getpass') @@ -118,3 +149,28 @@ def test_get_endpoint(self, mocked_input, getpass): # test-endpoint endpoint_url = config.get_endpoint_url(self.env) self.assertEqual(endpoint_url, 'test-endpoint') + + @mock.patch('SoftLayer.CLI.environment.Environment.input') + @mock.patch('SoftLayer.CLI.config.setup.get_sso_url') + @mock.patch('SoftLayer.CLI.config.setup.get_accounts') + @mock.patch('SoftLayer.API.IAMClient.authenticate_with_passcode') + @mock.patch('SoftLayer.API.IAMClient.refresh_iam_token') + @mock.patch('SoftLayer.API.IAMClient.call') + def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, mocked_input): + """Tests to make sure directly using an endpoint works""" + mocked_input.side_effect = ['n', '123qweasd'] + get_sso_url.return_value = "https://test.com/" + get_accounts.return_value = {"account_id": 12345, "ims_id": 5555} + passcode.return_value = {"access_token": "aassddffggh", "refresh_token": "qqqqqqq"} + token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} + test_key = "zz112233" + user_object_1 = { + "apiAuthenticationKeys": [{"authenticationKey":test_key}], + "username":"testerson", + "id":99} + api_call.side_effect = [user_object_1] + + user, apikey = config.sso_login(self.env) + self.assertEqual("testerson", user) + self.assertEqual(test_key, apikey) + \ No newline at end of file From 485d9f94a0a41e705c53ff60ae48c26c23b28797 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 16:59:44 -0500 Subject: [PATCH 0603/1582] Added -a as an option Co-authored-by: ATGE <30413337+ATGE@users.noreply.github.com> --- SoftLayer/CLI/config/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/config/setup.py b/SoftLayer/CLI/config/setup.py index d5a8ee6f2..261139a26 100644 --- a/SoftLayer/CLI/config/setup.py +++ b/SoftLayer/CLI/config/setup.py @@ -47,7 +47,7 @@ def get_api_key(client, username, secret): # pylint: disable=inconsistent-retur @click.command() -@click.option('--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), +@click.option('-a', '--auth', type=click.Choice(['ibmid', 'cloud_key', 'classic_key', 'sso']), help="Select a method of authentication.", default='classic_key', show_default=True) @environment.pass_env def cli(env, auth): From 3e34733e76a4d9f0dbb07a7be1327418af313fd9 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:24:26 -0500 Subject: [PATCH 0604/1582] tox fixes --- SoftLayer/API.py | 6 +++--- SoftLayer/config.py | 8 ++++++++ SoftLayer/exceptions.py | 1 + tests/CLI/modules/config_tests.py | 7 +++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a21f9f654..a32491ba7 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -405,7 +405,7 @@ def authenticate_with_password(self, username, password, security_question_id=No error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -452,7 +452,7 @@ def authenticate_with_passcode(self, passcode): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex self.settings['softlayer']['access_token'] = tokens['access_token'] self.settings['softlayer']['refresh_token'] = tokens['refresh_token'] @@ -516,7 +516,7 @@ def refresh_iam_token(self, r_token, account_id=None, ims_account=None): error = json.loads(response.text) raise exceptions.IAMError(response.status_code, error.get('errorMessage'), - 'https://iam.cloud.ibm.com/identity/token') + 'https://iam.cloud.ibm.com/identity/token') from ex a_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['expiration'])) r_expire = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(tokens['refresh_token_expiration'])) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 2f48aa221..291523d6c 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -102,6 +102,14 @@ def get_config(config_file=None): config_file = '~/.softlayer' config = configparser.ConfigParser() config.read(os.path.expanduser(config_file)) + # No configuration file found. + if not config.has_section('softlayer'): + config.add_section('softlayer') + config['softlayer']['username'] = '' + config['softlayer']['endpoint_url'] = '' + config['softlayer']['api_key'] = '' + config['softlayer']['timeout'] = 0 + return config diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index f444b8389..d7ce41bc3 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -15,6 +15,7 @@ class SoftLayerError(Exception): class Unauthenticated(SoftLayerError): """Unauthenticated.""" + class IAMError(SoftLayerError): """Errors from iam.cloud.ibm.com""" diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 7783ac2a2..5fe917c1c 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -165,12 +165,11 @@ def test_sso_login(self, api_call, token, passcode, get_accounts, get_sso_url, m token.return_value = {"access_token": "zzzzzz", "refresh_token": "fffffff"} test_key = "zz112233" user_object_1 = { - "apiAuthenticationKeys": [{"authenticationKey":test_key}], - "username":"testerson", - "id":99} + "apiAuthenticationKeys": [{"authenticationKey": test_key}], + "username": "testerson", + "id": 99} api_call.side_effect = [user_object_1] user, apikey = config.sso_login(self.env) self.assertEqual("testerson", user) self.assertEqual(test_key, apikey) - \ No newline at end of file From e20ceea73bb670183fe22a7f31c493d5e0d8a374 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 29 Mar 2021 17:34:44 -0500 Subject: [PATCH 0605/1582] fixing a test --- SoftLayer/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 291523d6c..caa8def10 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -108,7 +108,7 @@ def get_config(config_file=None): config['softlayer']['username'] = '' config['softlayer']['endpoint_url'] = '' config['softlayer']['api_key'] = '' - config['softlayer']['timeout'] = 0 + config['softlayer']['timeout'] = '0' return config From 0f4b627ba74e303d6309cb5eb3df1ce0d211580f Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 31 Mar 2021 16:19:23 -0500 Subject: [PATCH 0606/1582] #1395 Forced reserved capacity guests to be monthly as hourly gets ordered as the wrong package --- SoftLayer/CLI/virt/capacity/create_guest.py | 5 ++++- SoftLayer/managers/vs_capacity.py | 3 +++ tests/managers/vs/vs_capacity_tests.py | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/virt/capacity/create_guest.py b/SoftLayer/CLI/virt/capacity/create_guest.py index 4b8a983a6..07d215205 100644 --- a/SoftLayer/CLI/virt/capacity/create_guest.py +++ b/SoftLayer/CLI/virt/capacity/create_guest.py @@ -34,7 +34,10 @@ help="Test order, will return the order container, but not actually order a server.") @environment.pass_env def cli(env, **args): - """Allows for creating a virtual guest in a reserved capacity.""" + """Allows for creating a virtual guest in a reserved capacity. Only MONTHLY guests are supported at this time. + + If you would like support for hourly reserved capacity guests, please open an issue on the softlayer-python github. + """ create_args = _parse_create_args(env.client, args) create_args['primary_disk'] = args.get('primary_disk') diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 8ce5cf250..727a881b9 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -151,6 +151,9 @@ def create_guest(self, capacity_id, test, guest_object): # Reserved capacity only supports SAN as of 20181008 guest_object['local_disk'] = False + # Reserved capacity only supports monthly ordering via Virtual_Guest::generateOrderTemplate + # Hourly ordering would require building out the order manually. + guest_object['hourly'] = False template = vs_manager.verify_create_instance(**guest_object) template['reservedCapacityId'] = capacity_id if guest_object.get('ipv6'): diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index bb7178055..c6aad9f56 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -116,7 +116,6 @@ def test_create_guest(self): 'disks': (), 'domain': 'test.com', 'hostname': 'A1538172419', - 'hourly': True, 'ipv6': True, 'local_disk': None, 'os_code': 'UBUNTU_LATEST_64', @@ -132,7 +131,7 @@ def test_create_guest(self): 'maxMemory': None, 'hostname': 'A1538172419', 'domain': 'test.com', - 'hourlyBillingFlag': True, + 'hourlyBillingFlag': False, 'supplementalCreateObjectOptions': { 'bootMode': None, 'flavorKeyName': 'B1_1X2X25' From 848d27bb03a279d0758ccab2bb9dbd732b65ac4b Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:33:33 -0400 Subject: [PATCH 0607/1582] Add the option add and upgrade the hw disk. --- SoftLayer/CLI/hardware/upgrade.py | 24 +++++- .../fixtures/SoftLayer_Hardware_Server.py | 39 ++++++++++ SoftLayer/managers/hardware.py | 75 ++++++++++++++++++- tests/CLI/modules/server_tests.py | 35 +++++++++ tests/managers/hardware_tests.py | 31 ++++++++ 5 files changed, 197 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d766000ab..037506df0 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -24,16 +24,22 @@ default=None, type=click.Choice(['Non-RAID', 'RAID'])) @click.option('--public-bandwidth', type=click.INT, help="Public Bandwidth in GB") +@click.option('--add-disk', nargs=2, multiple=True, type=(int, int), + help="Add a Hard disk in GB to a specific channel, e.g 1000 GB in disk2, it will be " + "--add-disk 1000 2") +@click.option('--resize-disk', nargs=2, multiple=True, type=(int, int), + help="Upgrade a specific disk size in GB, e.g --resize-disk 2000 2") @click.option('--test', is_flag=True, default=False, help="Do not actually upgrade the hardware server") @environment.pass_env -def cli(env, identifier, memory, network, drive_controller, public_bandwidth, test): +def cli(env, identifier, memory, network, drive_controller, public_bandwidth, add_disk, resize_disk, test): """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) - if not any([memory, network, drive_controller, public_bandwidth]): + if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " - " [--memory], [--network], [--drive-controller], or [--public-bandwidth]") + " [--memory], [--network], [--drive-controller], [--public-bandwidth]," + "[--add-disk] or [--resize-disk]") hw_id = helpers.resolve_id(mgr.resolve_ids, identifier, 'Hardware') if not test: @@ -41,7 +47,17 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, te "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') + disk_list = list() + if add_disk: + for guest_disk in add_disk: + disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if resize_disk: + for guest_disk in resize_disk: + disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} + disk_list.append(disks) + if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, test=test): + public_bandwidth=public_bandwidth, disk=disk_list, test=test): raise exceptions.CLIAbort('Hardware Server Upgrade Failed') env.fout('Successfully Upgraded.') diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index a28d0fc13..0eacab158 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -13,6 +13,10 @@ 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], + 'nextInvoiceChildren': [ + {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1, 'categoryCode': 'disk1'}, + {'description': 'test2', 'nextInvoiceTotalRecurringAmount': 2, 'categoryCode': 'disk3'} + ], 'orderItem': { 'order': { 'userRecord': { @@ -336,5 +340,40 @@ "id": 6177, "keyName": "BANDWIDTH_500_GB" } + }, + { + "hourlyRecurringFee": ".023", + "id": 49759, + "recurringFee": "15", + "categories": [ + { + "categoryCode": "disk2", + "id": 6, + "name": "Third Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2", + } + }, + { + "id": 49759, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "disk1", + "id": 5, + "name": "Second Hard Drive" + } + ], + "item": { + "capacity": "1000", + "description": "1.00 TB SATA", + "id": 6159, + "keyName": "HARD_DRIVE_1_00_TB_SATA_2" + } } ] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d19a4c423..0ae4929b8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -818,7 +818,7 @@ def authorize_storage(self, hardware_id, username_storage): def upgrade(self, instance_id, memory=None, nic_speed=None, drive_controller=None, - public_bandwidth=None, test=False): + public_bandwidth=None, disk=None, test=False): """Upgrades a hardware server instance. :param int instance_id: Instance id of the hardware server to be upgraded. @@ -826,6 +826,7 @@ def upgrade(self, instance_id, memory=None, :param string nic_speed: Network Port Speed data. :param string drive_controller: Drive Controller data. :param int public_bandwidth: Public keyName data. + :param list disk: List of disks to add or upgrade Hardware Server. :param bool test: Test option to verify the request. :returns: bool @@ -857,6 +858,10 @@ def upgrade(self, instance_id, memory=None, 'packageId': package_id } + if disk: + prices = self._get_disk_price_list(instance_id, disk) + order['prices'] = prices + for option, value in data.items(): price_id = self._get_prices_for_upgrade_option(upgrade_prices, option, value) if not price_id: @@ -885,7 +890,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName]]' + 'billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) @@ -924,7 +929,10 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): 'disk_controller': 'disk_controller', 'bandwidth': 'bandwidth' } - category_code = option_category.get(option) + if 'disk' in option: + category_code = option + else: + category_code = option_category.get(option) for price in upgrade_prices: if price.get('categories') is None or price.get('item') is None: @@ -950,12 +958,73 @@ def _get_prices_for_upgrade_option(upgrade_prices, option, value): elif option == 'bandwidth': if str(product.get('capacity')) == str(value): price_id = price.get('id') + elif 'disk' in option: + if str(product.get('capacity')) == str(value): + price_id = price else: if str(product.get('capacity')) == str(value): price_id = price.get('id') return price_id + def _get_disk_price_list(self, instance_id, disk): + """Get the disks prices to be added or upgraded. + + :param int instance_id: Hardware Server instance id. + :param list disk: List of disks to be added o upgraded to the HW. + + :return list. + """ + prices = [] + disk_exist = False + upgrade_prices = self._get_upgrade_prices(instance_id) + server_response = self.get_instance(instance_id) + for disk_data in disk: + disk_channel = 'disk' + str(disk_data.get('number')) + for item in utils.lookup(server_response, 'billingItem', 'nextInvoiceChildren'): + if disk_channel == item['categoryCode']: + disk_exist = True + break + if disk_exist: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'add_disk') + prices.append(disk_price_detail) + else: + disk_price_detail = self._get_disk_price_detail(disk_data, upgrade_prices, disk_channel, 'resize_disk') + prices.append(disk_price_detail) + + return prices + + def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_type): + """Get the disk price detail. + + :param disk_data: List of disks to be added or upgraded. + :param list upgrade_prices: List of item prices. + :param String disk_channel: Disk position. + :param String disk_type: Disk type. + + """ + if disk_data.get('description') == disk_type: + raise SoftLayerError("Unable to add the disk because this already exists." + if "add" in disk_type else "Unable to resize the disk because this does not exists.") + else: + price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, + disk_data.get('capacity')) + if not price_id: + raise SoftLayerError("The item price was not found for %s with 'capacity:' %i" % + (disk_channel, disk_data.get('capacity'))) + + disk_price = { + "id": price_id.get('id'), + "categories": [ + { + "categoryCode": price_id['categories'][0]['categoryCode'], + "id": price_id['categories'][0]['id'] + } + ] + } + + return disk_price + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..f378e9745 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -944,6 +944,41 @@ def test_upgrade_test(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_add_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_resize_disk(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) + + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_not_price_found(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_already_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '1']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + + @mock.patch('SoftLayer.CLI.formatting.confirm') + def test_upgrade_disk_does_not_exist(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '3']) + self.assertEqual(result.exit_code, 2) + self.assertIsInstance(result.exception, exceptions.CLIAbort) + @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..f7f7c4ea2 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -869,6 +869,13 @@ def test_get_price_id_mismatch_capacity(self): result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'memory', 1) self.assertEqual(92, result) + def test_get_price_id_disk_capacity(self): + upgrade_prices = [ + {'categories': [{'categoryCode': 'disk1'}], 'item': {'capacity': 1}, 'id': 99} + ] + result = self.hardware._get_prices_for_upgrade_option(upgrade_prices, 'disk1', 1) + self.assertEqual(99, result['id']) + def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) @@ -878,6 +885,30 @@ def test_upgrade(self): order_container = call.args[0] self.assertEqual(order_container['prices'], [{'id': 209391}]) + def test_upgrade_add_disk(self): + disk_list = list() + disks = {'description': 'add_disk', 'capacity': 1000, 'number': 2} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + + def test_upgrade_resize_disk(self): + disk_list = list() + disks = {'description': 'resize_disk', 'capacity': 1000, 'number': 1} + disk_list.append(disks) + result = self.hardware.upgrade(1, disk=disk_list) + + self.assertEqual(result, True) + self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') + call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] + order_container = call.args[0] + self.assertEqual(order_container['prices'][0]['id'], 49759) + def test_upgrade_blank(self): result = self.hardware.upgrade(1) From 791c18dd4093d65e5051b2df96de3e68fa21947a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 18:54:17 -0400 Subject: [PATCH 0608/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0ae4929b8..34a724710 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1004,8 +1004,12 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: - raise SoftLayerError("Unable to add the disk because this already exists." - if "add" in disk_type else "Unable to resize the disk because this does not exists.") + if "add" in disk_type: + disk_type_description = "Unable to add the disk because this already exists." + else: + disk_type_description = "Unable to resize the disk because this does not exists." + + raise SoftLayerError(disk_type_description) else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 6885a47d5513ddceed00fac2f5d71a3d6314ce6d Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 19:53:11 -0400 Subject: [PATCH 0609/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 34a724710..0c290696f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1005,11 +1005,9 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t """ if disk_data.get('description') == disk_type: if "add" in disk_type: - disk_type_description = "Unable to add the disk because this already exists." + raise SoftLayerError("Unable to add the disk because this already exists.") else: - disk_type_description = "Unable to resize the disk because this does not exists." - - raise SoftLayerError(disk_type_description) + raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, disk_data.get('capacity')) From 24f7bb9e23c3d7d7153c9f4c1d71831a1c16a2a5 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Apr 2021 20:02:23 -0400 Subject: [PATCH 0610/1582] Fix tox analysis. --- SoftLayer/managers/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0c290696f..e161f6ef7 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1006,7 +1006,7 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t if disk_data.get('description') == disk_type: if "add" in disk_type: raise SoftLayerError("Unable to add the disk because this already exists.") - else: + if "resize" in disk_type: raise SoftLayerError("Unable to resize the disk because this does not exists.") else: price_id = self._get_prices_for_upgrade_option(upgrade_prices, disk_channel, From fb2b08ab826b64f34f739981dc173c3930e5131d Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 15:23:27 -0500 Subject: [PATCH 0611/1582] #1352 removing the rwhois commands --- SoftLayer/CLI/routes.py | 4 -- SoftLayer/CLI/rwhois/__init__.py | 1 - SoftLayer/CLI/rwhois/edit.py | 55 --------------------- SoftLayer/CLI/rwhois/show.py | 34 ------------- SoftLayer/managers/network.py | 37 +------------- docs/cli/rwhois.rst | 12 ----- tests/CLI/modules/rwhois_tests.py | 81 ------------------------------- tests/managers/network_tests.py | 39 --------------- 8 files changed, 1 insertion(+), 262 deletions(-) delete mode 100644 SoftLayer/CLI/rwhois/__init__.py delete mode 100644 SoftLayer/CLI/rwhois/edit.py delete mode 100644 SoftLayer/CLI/rwhois/show.py delete mode 100644 docs/cli/rwhois.rst delete mode 100644 tests/CLI/modules/rwhois_tests.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 1bb6f5d9c..6307c3a5e 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -231,10 +231,6 @@ ('order:quote', 'SoftLayer.CLI.order.quote:cli'), ('order:lookup', 'SoftLayer.CLI.order.lookup:cli'), - ('rwhois', 'SoftLayer.CLI.rwhois'), - ('rwhois:edit', 'SoftLayer.CLI.rwhois.edit:cli'), - ('rwhois:show', 'SoftLayer.CLI.rwhois.show:cli'), - ('hardware', 'SoftLayer.CLI.hardware'), ('hardware:bandwidth', 'SoftLayer.CLI.hardware.bandwidth:cli'), ('hardware:cancel', 'SoftLayer.CLI.hardware.cancel:cli'), diff --git a/SoftLayer/CLI/rwhois/__init__.py b/SoftLayer/CLI/rwhois/__init__.py deleted file mode 100644 index ef14d6880..000000000 --- a/SoftLayer/CLI/rwhois/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Referral Whois.""" diff --git a/SoftLayer/CLI/rwhois/edit.py b/SoftLayer/CLI/rwhois/edit.py deleted file mode 100644 index 01854bdc9..000000000 --- a/SoftLayer/CLI/rwhois/edit.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Edit the RWhois data on the account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions - - -@click.command() -@click.option('--abuse', help='Set the abuse email address') -@click.option('--address1', help='Update the address 1 field') -@click.option('--address2', help='Update the address 2 field') -@click.option('--city', help='Set the city name') -@click.option('--company', help='Set the company name') -@click.option('--country', help='Set the two-letter country code') -@click.option('--firstname', help='Update the first name field') -@click.option('--lastname', help='Update the last name field') -@click.option('--postal', help='Set the postal code field') -@click.option('--public/--private', - default=None, - help='Flags the address as a public or private residence.') -@click.option('--state', help='Set the two-letter state code') -@environment.pass_env -def cli(env, abuse, address1, address2, city, company, country, firstname, - lastname, postal, public, state): - """Edit the RWhois data on the account.""" - mgr = SoftLayer.NetworkManager(env.client) - - update = { - 'abuse_email': abuse, - 'address1': address1, - 'address2': address2, - 'company_name': company, - 'city': city, - 'country': country, - 'first_name': firstname, - 'last_name': lastname, - 'postal_code': postal, - 'state': state, - 'private_residence': public, - } - - if public is True: - update['private_residence'] = False - elif public is False: - update['private_residence'] = True - - check = [x for x in update.values() if x is not None] - if not check: - raise exceptions.CLIAbort( - "You must specify at least one field to update.") - - mgr.edit_rwhois(**update) diff --git a/SoftLayer/CLI/rwhois/show.py b/SoftLayer/CLI/rwhois/show.py deleted file mode 100644 index 9e862b0f3..000000000 --- a/SoftLayer/CLI/rwhois/show.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Display the RWhois information for your account.""" -# :license: MIT, see LICENSE for more details. - -import click - -import SoftLayer -from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - - -@click.command() -@environment.pass_env -def cli(env): - """Display the RWhois information for your account.""" - - mgr = SoftLayer.NetworkManager(env.client) - result = mgr.get_rwhois() - - table = formatting.KeyValueTable(['name', 'value']) - table.align['name'] = 'r' - table.align['value'] = 'l' - table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) - table.add_row(['Company', result['companyName']]) - table.add_row(['Abuse Email', result['abuseEmail']]) - table.add_row(['Address 1', result['address1']]) - if result.get('address2'): - table.add_row(['Address 2', result['address2']]) - table.add_row(['City', result['city']]) - table.add_row(['State', result.get('state', '-')]) - table.add_row(['Postal Code', result.get('postalCode', '-')]) - table.add_row(['Country', result['country']]) - table.add_row(['Private Residence', result['privateResidenceFlag']]) - - env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 11ed9733e..d609de5d5 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -56,7 +56,7 @@ class NetworkManager(object): - """Manage SoftLayer network objects: VLANs, subnets, IPs and rwhois + """Manage SoftLayer network objects: VLANs, subnets and IPs See product information here: https://www.ibm.com/cloud/network @@ -311,34 +311,6 @@ def detach_securitygroup_components(self, group_id, component_ids): return self.security_group.detachNetworkComponents(component_ids, id=group_id) - def edit_rwhois(self, abuse_email=None, address1=None, address2=None, - city=None, company_name=None, country=None, - first_name=None, last_name=None, postal_code=None, - private_residence=None, state=None): - """Edit rwhois record.""" - update = {} - for key, value in [('abuseEmail', abuse_email), - ('address1', address1), - ('address2', address2), - ('city', city), - ('companyName', company_name), - ('country', country), - ('firstName', first_name), - ('lastName', last_name), - ('privateResidenceFlag', private_residence), - ('state', state), - ('postalCode', postal_code)]: - if value is not None: - update[key] = value - - # If there's anything to update, update it - if update: - rwhois = self.get_rwhois() - return self.client['Network_Subnet_Rwhois_Data'].editObject( - update, id=rwhois['id']) - - return True - def edit_securitygroup(self, group_id, name=None, description=None): """Edit security group details. @@ -408,13 +380,6 @@ def ip_lookup(self, ip_address): obj = self.client['Network_Subnet_IpAddress'] return obj.getByIpAddress(ip_address, mask='hardware, virtualGuest') - def get_rwhois(self): - """Returns the RWhois information about the current account. - - :returns: A dictionary containing the account's RWhois information. - """ - return self.account.getRwhoisData() - def get_securitygroup(self, group_id, **kwargs): """Returns the information about the given security group. diff --git a/docs/cli/rwhois.rst b/docs/cli/rwhois.rst deleted file mode 100644 index 10d2004c9..000000000 --- a/docs/cli/rwhois.rst +++ /dev/null @@ -1,12 +0,0 @@ -.. _cli_rwhois: - -Reverse Whois Commands -====================== - -.. click:: SoftLayer.CLI.rwhois.edit:cli - :prog: rwhois edit - :show-nested: - -.. click:: SoftLayer.CLI.rwhois.show:cli - :prog: rwhois show - :show-nested: diff --git a/tests/CLI/modules/rwhois_tests.py b/tests/CLI/modules/rwhois_tests.py deleted file mode 100644 index 6409bf884..000000000 --- a/tests/CLI/modules/rwhois_tests.py +++ /dev/null @@ -1,81 +0,0 @@ -""" - SoftLayer.tests.CLI.modules.rwhois_tests - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - :license: MIT, see LICENSE for more details. -""" -from SoftLayer.CLI import exceptions -from SoftLayer import testing - -import json - - -class RWhoisTests(testing.TestCase): - def test_edit_nothing(self): - - result = self.run_command(['rwhois', 'edit']) - - self.assertEqual(result.exit_code, 2) - self.assertIsInstance(result.exception, exceptions.CLIAbort) - - def test_edit(self): - - result = self.run_command(['rwhois', 'edit', - '--abuse=abuse@site.com', - '--address1=address line 1', - '--address2=address line 2', - '--company=Company, Inc', - '--city=Dallas', - '--country=United States', - '--firstname=John', - '--lastname=Smith', - '--postal=12345', - '--state=TX', - '--state=TX', - '--private']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'city': 'Dallas', - 'firstName': 'John', - 'companyName': 'Company, Inc', - 'address1': 'address line 1', - 'address2': 'address line 2', - 'lastName': 'Smith', - 'abuseEmail': 'abuse@site.com', - 'state': 'TX', - 'country': 'United States', - 'postalCode': '12345', - 'privateResidenceFlag': True},), - identifier='id') - - def test_edit_public(self): - result = self.run_command(['rwhois', 'edit', '--public']) - - self.assert_no_fail(result) - self.assertEqual(result.output, "") - - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - args=({'privateResidenceFlag': False},), - identifier='id') - - def test_show(self): - self.maxDiff = 100000 - result = self.run_command(['rwhois', 'show']) - - expected = {'Abuse Email': 'abuseEmail', - 'Address 1': 'address1', - 'Address 2': 'address2', - 'City': 'city', - 'Company': 'companyName', - 'Country': 'country', - 'Name': 'firstName lastName', - 'Postal Code': 'postalCode', - 'State': '-', - 'Private Residence': True} - self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), expected) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 361ea1a61..80d054f9d 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -222,39 +222,6 @@ def test_detach_securitygroup_components(self): 'detachNetworkComponents', identifier=100, args=([500, 600],)) - def test_edit_rwhois(self): - result = self.network.edit_rwhois( - abuse_email='abuse@test.foo', - address1='123 Test Street', - address2='Apt. #31', - city='Anywhere', - company_name='TestLayer', - country='US', - first_name='Bob', - last_name='Bobinson', - postal_code='9ba62', - private_residence=False, - state='TX') - - self.assertEqual(result, True) - expected = { - 'abuseEmail': 'abuse@test.foo', - 'address1': '123 Test Street', - 'address2': 'Apt. #31', - 'city': 'Anywhere', - 'companyName': 'TestLayer', - 'country': 'US', - 'firstName': 'Bob', - 'lastName': 'Bobinson', - 'postalCode': '9ba62', - 'privateResidenceFlag': False, - 'state': 'TX', - } - self.assert_called_with('SoftLayer_Network_Subnet_Rwhois_Data', - 'editObject', - identifier='id', - args=(expected,)) - def test_edit_securitygroup(self): result = self.network.edit_securitygroup(100, name='foobar') @@ -290,12 +257,6 @@ def test_edit_securitygroup_rule_unset(self): 'portRangeMin': -1, 'portRangeMax': -1, 'ethertype': '', 'remoteIp': ''}],)) - def test_get_rwhois(self): - result = self.network.get_rwhois() - - self.assertEqual(result, fixtures.SoftLayer_Account.getRwhoisData) - self.assert_called_with('SoftLayer_Account', 'getRwhoisData') - def test_get_securitygroup(self): result = self.network.get_securitygroup(100) From 262507e07c861ee249f959d2301cdc1d899789a1 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Apr 2021 16:07:00 -0500 Subject: [PATCH 0612/1582] #1457 added contributing guide --- CONTRIBUTING.md | 62 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1eed6d308..9182f802b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,14 +3,22 @@ We are happy to accept contributions to softlayer-python. Please follow the guidelines below. -* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/dev/cla-individual.md). +## Procedural -* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). - -* Fork the repo, make your changes, and open a pull request. +1. All code changes require a corresponding issue. [Open an issue here](https://github.com/softlayer/softlayer-python/issues). +2. Fork the [softlayer-python](https://github.com/softlayer/softlayer-python) repository. +3. Make any changes required, commit messages should reference the issue number (include #1234 if the message if your issue is number 1234 for example). +4. Make a pull request from your fork/branch to origin/master +5. Requires 1 approval for merging * Additional infomration can be found in our [contribution guide](http://softlayer-python.readthedocs.org/en/latest/dev/index.html) +## Legal + +* See our [Contributor License Agreement](./docs/dev/cla-individual.md). Opening a pull request is acceptance of this agreement. + +* If you're contributing on behalf of your employer we'll need a signed copy of our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/dev/cla-corporate.md). + ## Code style @@ -101,4 +109,48 @@ order_args = getattr(order_call[0], 'args')[0] # Test our specific argument value self.assertEqual(123, order_args['hostId']) -``` \ No newline at end of file +``` + + +## Project Management + +### Issues + +* ~~Title~~: Should contain quick highlight of the issue is about +* ~~Body~~: All the technical information goes here +* ~~Assignee~~: Should be the person who is actively working on an issue. +* ~~Label~~: All issues should have at least 1 Label. +* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. + +### Pull Requests + +* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* ~~Reviewers~~: 1 Reviewer is required +* ~~Assignee~~: Should be the person who opened the pull request +* ~~Labels~~: Should match the issue +* ~~Projects~~: Should match the issue +* ~~Milestones~~: Not really used, can be left blank +* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. + +### Code Reviews +All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. + +#### Things to look for while doing a review + +As a reviewer, these are some guidelines when doing a review, but not hard rules. + +* Code Style: Generally `tox -e analysis` will pick up most style violations, but anything that is wildly different from the normal code patters in this project should be changed to match, unless there is a strong reason to not do so. +* API Calls: Close attention should be made to any new API calls, to make sure they will work as expected, and errors are handled if needed. +* DocBlock comments: CLI and manager methods need to be documented well enough for users to easily understand what they do. +* Easy to read code: Code should generally be easy enough to understand at a glance. Confusing code is a sign that it should either be better documented, or refactored a bit to be clearer in design. + + +### Testing + +When doing testing of a code change, indicate this with a comment on the pull request like + +:heavy_check: `slcli vs list --new-feature` +:x: `slcli vs list --broken-feature` From 26d9162db780889617e0f6071b1b1bc1db0365d9 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 09:01:27 -0400 Subject: [PATCH 0613/1582] Add an --orderBy parameters to call-api --- SoftLayer/CLI/call_api.py | 8 +++++++- SoftLayer/utils.py | 16 ++++++++++++++++ tests/CLI/modules/call_api_tests.py | 14 ++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cbce4eccb..cf0a2b871 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,6 +112,9 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") +@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" + "E.G --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -119,7 +122,7 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument "Remember to use double quotes (\") for variable names. Can NOT be used with --filter. " "Dont use whitespace outside of strings, or the slcli might have trouble parsing it.") @environment.pass_env -def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, +def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, orderby=None, output_python=False, json_filter=None): """Call arbitrary API endpoints with the given SERVICE and METHOD. @@ -147,6 +150,9 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, raise exceptions.CLIAbort("--filter and --json-filter cannot be used together.") object_filter = _build_filters(_filters) + if orderby: + _filters = utils.build_filter_orderby(orderby) + object_filter.update(_filters) if json_filter: object_filter.update(json_filter) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..3900bb9dd 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -200,6 +200,22 @@ def event_log_filter_less_than_date(date, utc): } +def build_filter_orderby(orderby): + _filters = {} + aux = list(reversed(str(orderby).split('.'))) + for split in aux: + _aux_filter = {} + if str(split).__contains__('='): + _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + _filters = _aux_filter + elif split == list(aux)[0]: + _aux_filter[split] = query_filter_orderby('DESC') + else: + _aux_filter[split] = _filters + _filters = _aux_filter + return _filters + + class IdentifierMixin(object): """Mixin used to resolve ids from other names of objects. diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index 8d3f19ab2..b98998f29 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -298,3 +298,17 @@ def test_json_filter(self): result = self.run_command(['call-api', 'Account', 'getObject', '--json-filter={"test":"something"}']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getObject', filter={"test": "something"}) + + def test_call_api_orderBy(self): + result = self.run_command(['call-api', 'Account', 'getVirtualGuests', + '--orderBy', 'virtualGuests.id=DESC']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', + 'getVirtualGuests', + filter={ + 'virtualGuests': + {'id': { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}}}) From c232336bcf8e46f4181e3e5e85d62806b9f69453 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 11:16:46 -0400 Subject: [PATCH 0614/1582] Add method comment --- SoftLayer/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 3900bb9dd..f5cdce405 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,6 +201,12 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): + """ + Builds filters using the filter options passed into the CLI. + + only support fot create filter option orderBy, default value is DESC + + """ _filters = {} aux = list(reversed(str(orderby).split('.'))) for split in aux: From 447b3aa5303be9005706b667ba37488b61e7ff0c Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 12:02:00 -0400 Subject: [PATCH 0615/1582] Add method comment --- SoftLayer/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index f5cdce405..ac2e34099 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -201,11 +201,9 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): - """ - Builds filters using the filter options passed into the CLI. - - only support fot create filter option orderBy, default value is DESC + """Builds filters using the filter options passed into the CLI. + Only support fot create filter option orderBy, default value is DESC. """ _filters = {} aux = list(reversed(str(orderby).split('.'))) From 19f28989606c30c404299659221066163111c591 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 7 Apr 2021 18:23:04 -0400 Subject: [PATCH 0616/1582] fix Christopher code review comments --- SoftLayer/CLI/hardware/detail.py | 7 +++++-- SoftLayer/managers/hardware.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index c5b1c2b9b..512850dba 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -61,8 +61,11 @@ def cli(env, identifier, passwords, price): table.add_row(['os_version', operating_system.get('version') or formatting.blank()]) table.add_row(['created', result['provisionDate'] or formatting.blank()]) table.add_row(['owner', owner or formatting.blank()]) - table.add_row(['last_transaction', - utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name')]) + + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 9045d9bda..ea2fb1a63 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -263,7 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): 'children[nextInvoiceTotalRecurringAmount],' 'orderItem.order.userRecord[username]' '],' - 'lastTransaction[transactionGroup[name]],' + 'lastTransaction[transactionGroup],' 'hourlyBillingFlag,' 'tagReferences[id,tag[name,id]],' 'networkVlans[id,vlanNumber,networkSpace],' From 10bfe14bb64f3e1ce189afc781387b0adfc3cced Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 14:34:44 -0500 Subject: [PATCH 0617/1582] #1436 added checking for a special character sequence for when windows users use shift+ins to paste into a password field --- SoftLayer/CLI/environment.py | 17 ++++++++++++++++- tests/CLI/environment_tests.py | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c73bc6385..b1670f949 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -67,7 +67,22 @@ def input(self, prompt, default=None, show_default=True): def getpass(self, prompt, default=None): """Provide a password prompt.""" - return click.prompt(prompt, hide_input=True, default=default) + password = click.prompt(prompt, hide_input=True, default=default) + + # https://github.com/softlayer/softlayer-python/issues/1436 + # click.prompt uses python's getpass() in the background + # https://github.com/python/cpython/blob/3.9/Lib/getpass.py#L97 + # In windows, shift+insert actually inputs the below 2 characters + # If we detect those 2 characters, need to manually read from the clipbaord instead + # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard + if password == 'àR': + # tkinter is a built in python gui, but it has clipboard reading functions. + from tkinter import Tk + tk_manager = Tk() + password = tk_manager.clipboard_get() + # keep the window from showing + tk_manager.withdraw() + return password # Command loading methods def list_commands(self, *path): diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..f000819a6 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -55,6 +55,15 @@ def test_getpass(self, prompt_mock): prompt_mock.assert_called_with('input', default=None, hide_input=True) self.assertEqual(prompt_mock(), r) + @mock.patch('click.prompt') + @mock.patch('tkinter.Tk.clipboard_get') + def test_getpass_issues1436(self, tk, prompt_mock): + tk.return_value = 'test_from_clipboard' + prompt_mock.return_value = 'àR' + r = self.env.getpass('input') + prompt_mock.assert_called_with('input', default=None, hide_input=True) + self.assertEqual('test_from_clipboard', r) + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From 391bf8ccd7e2537d3d213c449d928f83a1882070 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:13:48 -0500 Subject: [PATCH 0618/1582] fixing a unit test --- tests/CLI/environment_tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f000819a6..b6d275941 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -7,6 +7,7 @@ import click import mock +# from unittest.mock import MagicMock from SoftLayer.CLI import environment from SoftLayer import testing @@ -56,13 +57,13 @@ def test_getpass(self, prompt_mock): self.assertEqual(prompt_mock(), r) @mock.patch('click.prompt') - @mock.patch('tkinter.Tk.clipboard_get') + @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): - tk.return_value = 'test_from_clipboard' prompt_mock.return_value = 'àR' r = self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) - self.assertEqual('test_from_clipboard', r) + tk.assert_called_with() + def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} From 07df909a4321b77211db521158ba11073852e6a3 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 8 Apr 2021 15:32:58 -0500 Subject: [PATCH 0619/1582] tox fixes --- SoftLayer/CLI/environment.py | 1 + tests/CLI/environment_tests.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index b1670f949..e16c5cde9 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -77,6 +77,7 @@ def getpass(self, prompt, default=None): # https://stackoverflow.com/questions/101128/how-do-i-read-text-from-the-clipboard if password == 'àR': # tkinter is a built in python gui, but it has clipboard reading functions. + # pylint: disable=import-outside-toplevel from tkinter import Tk tk_manager = Tk() password = tk_manager.clipboard_get() diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index b6d275941..a7a91d0f2 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -60,11 +60,10 @@ def test_getpass(self, prompt_mock): @mock.patch('tkinter.Tk') def test_getpass_issues1436(self, tk, prompt_mock): prompt_mock.return_value = 'àR' - r = self.env.getpass('input') + self.env.getpass('input') prompt_mock.assert_called_with('input', default=None, hide_input=True) tk.assert_called_with() - def test_resolve_alias(self): self.env.aliases = {'aliasname': 'realname'} r = self.env.resolve_alias('aliasname') From c8bb4ff7c8157ee768c5cb4ac4c5975a1abbc530 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 9 Apr 2021 15:19:50 -0500 Subject: [PATCH 0620/1582] removed reference to rwhois in the documentation --- docs/cli.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index dc82da29f..a659b145c 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -92,7 +92,6 @@ To discover the available commands, simply type `slcli`. object-storage Object Storage. order View and order from the catalog. report Reports. - rwhois Referral Whois. securitygroup Network security groups. setup Edit configuration. shell Enters a shell for slcli. From 9c4f3ba229d29060a2960c6f224ea801e94207d9 Mon Sep 17 00:00:00 2001 From: Fernando Date: Fri, 9 Apr 2021 19:32:17 -0400 Subject: [PATCH 0621/1582] Fix slcli hw upgrade disk to support with rest. --- SoftLayer/managers/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index e161f6ef7..2b6eb7f8f 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -896,7 +896,7 @@ def get_instance(self, instance_id): return self.hardware.getObject(id=instance_id, mask=mask) - def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): + def _get_upgrade_prices(self, instance_id): """Following Method gets all the price ids related to upgrading a Hardware Server. :param int instance_id: Instance id of the Hardware Server to be upgraded. @@ -910,7 +910,7 @@ def _get_upgrade_prices(self, instance_id, include_downgrade_options=True): 'item[keyName,description,capacity,units]' ] mask = "mask[%s]" % ','.join(mask) - return self.hardware.getUpgradeItemPrices(include_downgrade_options, id=instance_id, mask=mask) + return self.hardware.getUpgradeItemPrices(id=instance_id, mask=mask) @staticmethod def _get_prices_for_upgrade_option(upgrade_prices, option, value): From f39538a3066229a3667f1e1afb705320d1ab9177 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 13 Apr 2021 15:49:31 -0400 Subject: [PATCH 0622/1582] add Billing and lastTransaction on slcli virtual detail --- SoftLayer/CLI/virt/detail.py | 5 +++++ SoftLayer/managers/vs.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/virt/detail.py b/SoftLayer/CLI/virt/detail.py index 01a66cc9f..ac9453ddd 100644 --- a/SoftLayer/CLI/virt/detail.py +++ b/SoftLayer/CLI/virt/detail.py @@ -69,6 +69,11 @@ def cli(env, identifier, passwords=False, price=False): table.add_row(['transient', result.get('transientGuestFlag', False)]) table.add_row(['created', result['createDate']]) table.add_row(['modified', result['modifyDate']]) + last_transaction = "{} ({})".format(utils.lookup(result, 'lastTransaction', 'transactionGroup', 'name'), + utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) + + table.add_row(['last_transaction', last_transaction]) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) table.add_row(['preset', utils.lookup(result, 'billingItem', 'orderItem', 'preset', diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index c0eb91b9a..77410ff4a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -227,7 +227,7 @@ def get_instance(self, instance_id, **kwargs): 'maxMemory,' 'datacenter,' 'activeTransaction[id, transactionStatus[friendlyName,name]],' - 'lastTransaction[transactionStatus],' + 'lastTransaction[transactionStatus,modifyDate,transactionGroup[name]],' 'lastOperatingSystemReload.id,' 'blockDevices,' 'blockDeviceTemplateGroup[id, name, globalIdentifier],' From 9e27d841e0b8512229839c45c29b19bb69545b5c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 15:34:13 -0500 Subject: [PATCH 0623/1582] #1462 Added automation to publish to test-pypi in preperation for fully automating the build process --- .github/workflows/test_pypi_release.yml | 37 ++++++++++++++ README.rst | 7 ++- RELEASE.md | 68 ++++++++++++++++++++++--- 3 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test_pypi_release.yml diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml new file mode 100644 index 000000000..5e7c6b683 --- /dev/null +++ b/.github/workflows/test_pypi_release.yml @@ -0,0 +1,37 @@ +# https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ + +name: Publish 📦 to TestPyPI + +on: + push: + branches: [ master ] + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/README.rst b/README.rst index 75e5d6f54..2ae928347 100644 --- a/README.rst +++ b/README.rst @@ -28,8 +28,7 @@ SoftLayer products and services. Documentation ------------- -Documentation for the Python client is available at -http://softlayer.github.io/softlayer-python/. +Documentation for the Python client is available at `Read the Docs `_ . Additional API documentation can be found on the SoftLayer Development Network: @@ -38,7 +37,7 @@ Additional API documentation can be found on the SoftLayer Development Network: * `Object mask information and examples `_ * `Code Examples - `_ + `_ Installation ------------ @@ -82,7 +81,7 @@ Issues with the Softlayer API itself should be addressed by opening a ticket. Examples -------- -A curated list of examples on how to use this library can be found at `softlayer.github.io `_ +A curated list of examples on how to use this library can be found at `SLDN `_ Debugging --------- diff --git a/RELEASE.md b/RELEASE.md index eb1cb6d47..962ee1663 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,18 +1,74 @@ -# Release steps -* Update version constants (find them by running `git grep [VERSION_NUMBER]`) -* Create changelog entry (edit CHANGELOG.md with a one-liner for each closed issue going in the release) -* Commit and push changes to master with the message: "Version Bump to v[VERSION_NUMBER]" -* Make sure your `upstream` repo is set + +# Versions + +This project follows the Major.Minor.Revision versioning system. Fixes, and minor additions would increment Revision. Large changes and additions would increment Minor, and anything that would be a "Breaking" change, or redesign would be an increment of Major. + +# Changelog + +When doing a release, the Changelog format should be as follows: + +```markdown + +## [Version] - YYYY-MM-DD +https://github.com/softlayer/softlayer-python/compare/v5.9.0...v5.9.1 + +#### New Command +- `slcli new command` #issueNumber + +#### Improvements +- List out improvements #issueNumber +- Something else that changed #issueNumber + +#### Deprecated +- List something that got removed #issueNumber + +``` + +# Normal Release steps + +A "release" of the softlayer-python project is the current state of the `master` branch. Any changes in the master branch should be considered releaseable. + + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Make sure the tests for the build all pass +4. [Draft a new release](https://github.com/softlayer/softlayer-python/releases/new) + - Version should start with `v` followed by Major.Minor.Revision: `vM.m.r` + - Title should be `M.m.r` + - Description should be the release notes + - Target should be the `master` branch +5. The github automation should take care of publishing the release to [PyPi](https://pypi.org/project/SoftLayer/). This may take a few minutes to update. + +# Manual Release steps + +1. Create the changelog entry, us this to update `CHANGELOG.md` and as the text for the release on github. +2. Update the version numbers in these files on the master branch. + - `SoftLayer/consts.py` + - `setup.py` +3. Commit your changes to `master`, and make sure `softlayer/softlayer-python` repo is updated to reflect that +4. Make sure your `upstream` repo is set + ``` git remote -v upstream git@github.com:softlayer/softlayer-python.git (fetch) upstream git@github.com:softlayer/softlayer-python.git (push) ``` -* Push tag and PyPi `python fabfile.py 5.7.2`. Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: + +5. Create and publish the package + - Make sure you have `twine` installed, this is what uploads the pacakge to PyPi. + - Before you do this, make sure you have the organization repository set up as upstream remote, also make sure that you have pip set up with your PyPi user credentials. The easiest way to do that is to create a file at `~/.pypirc` with the following contents: ``` [server-login] username:YOUR_USERNAME password:YOUR_PASSWORD ``` + + - Run `python fabfile.py 5.7.2`. Where `5.7.2` is the `M.m.r` version number. Don't use the `v` here in the version number. + + +*NOTE* PyPi doesn't let you reupload a version, if you upload a bad package for some reason, you have to create a new version. + From 53fc65625e25aa4206aa0712ce2811448836557c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:39:38 -0500 Subject: [PATCH 0624/1582] Added a utility to merge objectFilters, #1459 1461 --- SoftLayer/utils.py | 16 ++++++++++++++++ tests/basic_tests.py | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cc6d7bd4f..bd7f33c91 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +import collections import datetime import re import time @@ -57,6 +58,21 @@ def to_dict(self): for key, val in self.items()} +def dict_merge(dct1, dct2): + """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + + :param dct1: dict onto which the merge is executed + :param dct2: dct merged into dct + :return: None + """ + + for k, v in dct2.items(): + if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): + dict_merge(dct1[k], dct2[k]) + else: + dct1[k] = dct2[k] + + def query_filter(query): """Translate a query-style string to a 'filter'. diff --git a/tests/basic_tests.py b/tests/basic_tests.py index b430a3d5e..f4dbe6085 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -79,6 +79,16 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.dst()) self.assertEqual(datetime.timedelta(0), time.utcoffset()) + def test_dict_merge(self): + filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} + filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + SoftLayer.utils.dict_merge(filter1, filter2) + + self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') + self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + + + class TestNestedDict(testing.TestCase): From ffce9b3020437e99d46d850165c2d5a713dd7ad0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 16:48:06 -0500 Subject: [PATCH 0625/1582] changed dict_merge to return a merged dictionary --- SoftLayer/utils.py | 14 ++++++++------ tests/basic_tests.py | 7 ++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index bd7f33c91..c0851ec4a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -59,18 +59,20 @@ def to_dict(self): def dict_merge(dct1, dct2): - """Recursively merges dct2 into dct1, ideal for merging objectFilter together. + """Recursively merges dct2 and dct1, ideal for merging objectFilter together. - :param dct1: dict onto which the merge is executed - :param dct2: dct merged into dct - :return: None + :param dct1: A dictionary + :param dct2: A dictionary + :return: dct1 + dct2 """ + dct = dct1.copy() for k, v in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): - dict_merge(dct1[k], dct2[k]) + dct[k] = dict_merge(dct1[k], dct2[k]) else: - dct1[k] = dct2[k] + dct[k] = dct2[k] + return dct def query_filter(query): diff --git a/tests/basic_tests.py b/tests/basic_tests.py index f4dbe6085..dbbcbef0a 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -82,10 +82,11 @@ def test_timezone(self): def test_dict_merge(self): filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} - SoftLayer.utils.dict_merge(filter1, filter2) + result = SoftLayer.utils.dict_merge(filter1, filter2) - self.assertEqual(filter1['virtualGuests']['id']['operation'], 'orderBy') - self.assertEqual(filter1['virtualGuests']['hostname']['operation'], 'etst') + self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') + self.assertNotIn('id', filter1['virtualGuests']) + self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') From 554cbbd33fbc3c40c2751c8f2182e92c5d24a3ff Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 13 Apr 2021 18:42:37 -0500 Subject: [PATCH 0626/1582] tox fixes --- SoftLayer/utils.py | 2 +- tests/basic_tests.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index c0851ec4a..83bd79eae 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -67,7 +67,7 @@ def dict_merge(dct1, dct2): """ dct = dct1.copy() - for k, v in dct2.items(): + for k, _ in dct2.items(): if (k in dct1 and isinstance(dct1[k], dict) and isinstance(dct2[k], collections.Mapping)): dct[k] = dict_merge(dct1[k], dct2[k]) else: diff --git a/tests/basic_tests.py b/tests/basic_tests.py index dbbcbef0a..59bd76d86 100644 --- a/tests/basic_tests.py +++ b/tests/basic_tests.py @@ -80,8 +80,8 @@ def test_timezone(self): self.assertEqual(datetime.timedelta(0), time.utcoffset()) def test_dict_merge(self): - filter1 = {"virtualGuests":{"hostname":{"operation":"etst"}}} - filter2 = {"virtualGuests":{"id":{"operation":"orderBy","options":[{"name":"sort","value":["DESC"]}]}}} + filter1 = {"virtualGuests": {"hostname": {"operation": "etst"}}} + filter2 = {"virtualGuests": {"id": {"operation": "orderBy", "options": [{"name": "sort", "value": ["DESC"]}]}}} result = SoftLayer.utils.dict_merge(filter1, filter2) self.assertEqual(result['virtualGuests']['id']['operation'], 'orderBy') @@ -89,8 +89,6 @@ def test_dict_merge(self): self.assertEqual(result['virtualGuests']['hostname']['operation'], 'etst') - - class TestNestedDict(testing.TestCase): def test_basic(self): From 5caa6ce09f477f6f4215702bc32c945fbb968a7e Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 14 Apr 2021 14:23:37 -0500 Subject: [PATCH 0627/1582] Updating author_email to SLDN distro list --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6d51d38ce..a09cca0a4 100644 --- a/setup.py +++ b/setup.py @@ -19,8 +19,8 @@ version='5.9.3', description=DESCRIPTION, long_description=LONG_DESCRIPTION, - author='SoftLayer Technologies, Inc.', - author_email='sldn@softlayer.com', + author='SoftLayer, Inc., an IBM Company', + author_email='SLDNDeveloperRelations@wwpdl.vnet.ibm.com', packages=find_packages(exclude=['tests']), license='MIT', zip_safe=False, From 9a5f20ac0056cdbaa571395a05c1ad2ba0c5fb2c Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Thu, 15 Apr 2021 14:47:16 -0500 Subject: [PATCH 0628/1582] fixed some style issues --- CONTRIBUTING.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9182f802b..2ec9136a1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,24 +116,24 @@ self.assertEqual(123, order_args['hostId']) ### Issues -* ~~Title~~: Should contain quick highlight of the issue is about -* ~~Body~~: All the technical information goes here -* ~~Assignee~~: Should be the person who is actively working on an issue. -* ~~Label~~: All issues should have at least 1 Label. -* ~~Projects~~: Should be added to the quarerly Softlayer project when being worked on -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked Pull Request~~: Should be linked to the relavent pull request when it is opened. +* _Title_: Should contain quick highlight of the issue is about +* _Body_: All the technical information goes here +* _Assignee_: Should be the person who is actively working on an issue. +* _Label_: All issues should have at least 1 Label. +* _Projects_: Should be added to the quarerly Softlayer project when being worked on +* _Milestones_: Not really used, can be left blank +* _Linked Pull Request_: Should be linked to the relavent pull request when it is opened. ### Pull Requests -* ~~Title~~: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request -* ~~Body~~: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. -* ~~Reviewers~~: 1 Reviewer is required -* ~~Assignee~~: Should be the person who opened the pull request -* ~~Labels~~: Should match the issue -* ~~Projects~~: Should match the issue -* ~~Milestones~~: Not really used, can be left blank -* ~~Linked issues~~: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. +* _Title_: Should be similar to the title of the issue it is fixing, or otherwise descibe what is chaning in the pull request +* _Body_: Should have "Fixes #1234" at least, with some notes about the specific pull request if needed. Most technical information should still be in the github issue. +* _Reviewers_: 1 Reviewer is required +* _Assignee_: Should be the person who opened the pull request +* _Labels_: Should match the issue +* _Projects_: Should match the issue +* _Milestones_: Not really used, can be left blank +* _Linked issues_: If you put "Fixes #" in the body, this should be automatically filled in, otherwise link manually. ### Code Reviews All issues should be reviewed by at least 1 member of the SLDN team that is not the person opening the pull request. Time permitting, all members of the SLDN team should review the request. From 1af447c63b33047591516c9ec89347754d6257e0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 15:51:34 -0400 Subject: [PATCH 0629/1582] add the firewall information on slcli firewall detail --- SoftLayer/CLI/firewall/detail.py | 16 ++++++++++++++-- SoftLayer/managers/firewall.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 1beb1a32a..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,12 +19,24 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) + result = mgr.get_instance(firewall_id) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result['id']]) + table.add_row(['primaryIpAddress', result['primaryIpAddress']]) + table.add_row(['datacenter', result['datacenter']['longName']]) + table.add_row(['networkVlan', result['networkVlan']['name']]) + table.add_row(['networkVlaniD', result['networkVlan']['id']]) + if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - - env.fout(get_rules_table(rules)) + table.add_row(['rules', get_rules_table(rules)]) + env.fout(table) def get_rules_table(rules): diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 34b197521..633eddfd8 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -290,3 +290,15 @@ def edit_standard_fwl_rules(self, firewall_id, rules): template = {'networkComponentFirewallId': firewall_id, 'rules': rules} return rule_svc.createObject(template) + + def get_instance(self, firewall_id, mask=None): + """Get the firewall information + + :param integer firewall_id: the instance ID of the standard firewall + """ + if not mask: + mask = ('mask[datacenter,networkVlan]') + + svc = self.client['Network_Vlan_Firewall'] + + return svc.getObject(id=firewall_id, mask=mask) From 319121eec26ca1402f92ea870f873dec26e6a309 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 22 Apr 2021 16:41:18 -0400 Subject: [PATCH 0630/1582] fix tox tool --- .../SoftLayer_Network_Vlan_Firewall.py | 7 +++ tests/CLI/modules/firewall_tests.py | 47 ++++++++++--------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py index 5d78cf53b..c2f4134f3 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan_Firewall.py @@ -7,10 +7,17 @@ }, "id": 3130, "primaryIpAddress": "192.155.239.146", + "datacenter": { + "id": 265592, + "longName": "Amsterdam 1", + "name": "ams01", + "statusId": 2 + }, "networkVlan": { "accountId": 307608, "id": 371028, "primarySubnetId": 536252, + "name": 'testvlan', "vlanNumber": 1489, "firewallInterfaces": [ { diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..3fe9c3214 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -59,27 +59,32 @@ def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - [{'#': 1, - 'action': 'permit', - 'dest': 'any on server:80-80', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}, - {'#': 2, - 'action': 'permit', - 'dest': 'any on server:1-65535', - 'dest_mask': '255.255.255.255', - 'protocol': 'tmp', - 'src_ip': '193.212.1.10', - 'src_mask': '255.255.255.255'}, - {'#': 3, - 'action': 'permit', - 'dest': 'any on server:80-800', - 'dest_mask': '255.255.255.255', - 'protocol': 'tcp', - 'src_ip': '0.0.0.0', - 'src_mask': '0.0.0.0'}]) + {'datacenter': 'Amsterdam 1', + 'id': 3130, + 'networkVlan': 'testvlan', + 'networkVlaniD': 371028, + 'primaryIpAddress': '192.155.239.146', + 'rules': [{'#': 1, + 'action': 'permit', + 'dest': 'any on server:80-80', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}, + {'#': 2, + 'action': 'permit', + 'dest': 'any on server:1-65535', + 'dest_mask': '255.255.255.255', + 'protocol': 'tmp', + 'src_ip': '193.212.1.10', + 'src_mask': '255.255.255.255'}, + {'#': 3, + 'action': 'permit', + 'dest': 'any on server:80-800', + 'dest_mask': '255.255.255.255', + 'protocol': 'tcp', + 'src_ip': '0.0.0.0', + 'src_mask': '0.0.0.0'}]}) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_cancel_firewall(self, confirm_mock): From 7b70badb55ed13961fbea639f08fe66fe432259e Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 23 Apr 2021 10:40:30 -0400 Subject: [PATCH 0631/1582] update with Christopher method and fix the team code review comments --- SoftLayer/CLI/call_api.py | 17 +++++++++++------ SoftLayer/utils.py | 16 ++++++++-------- tests/CLI/modules/call_api_tests.py | 8 ++++++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/call_api.py b/SoftLayer/CLI/call_api.py index cf0a2b871..b07834ed6 100644 --- a/SoftLayer/CLI/call_api.py +++ b/SoftLayer/CLI/call_api.py @@ -112,9 +112,12 @@ def _validate_parameters(ctx, param, value): # pylint: disable=unused-argument @click.option('--mask', help="String-based object mask") @click.option('--limit', type=click.INT, help="Result limit") @click.option('--offset', type=click.INT, help="Result offset") -@click.option('--orderBy', type=click.STRING, help="an object filter that adds an order by clause" - "E.G --orderBy subnets.id default DESC" - " --orderBy subnets.id=ASC") +@click.option('--orderBy', type=click.STRING, + help="To set the sort direction, ASC or DESC can be provided." + "This should be of the form: '--orderBy nested.property' default DESC or " + "'--orderBy nested.property=ASC', e.g. " + " --orderBy subnets.id default DESC" + " --orderBy subnets.id=ASC") @click.option('--output-python / --no-output-python', help="Show python example code instead of executing the call") @click.option('--json-filter', callback=_validate_filter, @@ -144,6 +147,8 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or --json-filter '{"virtualGuests":{"hostname":{"operation":"^= test"}}}' --limit=10 slcli -v call-api SoftLayer_User_Customer addBulkPortalPermission --id=1234567 \\ '[{"keyName": "NETWORK_MESSAGE_DELIVERY_MANAGE"}]' + slcli call-api Account getVirtualGuests \\ + --orderBy virttualguests.id=ASC """ if _filters and json_filter: @@ -151,10 +156,10 @@ def cli(env, service, method, parameters, _id, _filters, mask, limit, offset, or object_filter = _build_filters(_filters) if orderby: - _filters = utils.build_filter_orderby(orderby) - object_filter.update(_filters) + orderby = utils.build_filter_orderby(orderby) + object_filter = utils.dict_merge(object_filter, orderby) if json_filter: - object_filter.update(json_filter) + object_filter = utils.dict_merge(json_filter, object_filter) args = [service, method] + list(parameters) kwargs = { diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index cce78839c..6b18b6570 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -221,19 +221,19 @@ def event_log_filter_less_than_date(date, utc): def build_filter_orderby(orderby): """Builds filters using the filter options passed into the CLI. - Only support fot create filter option orderBy, default value is DESC. + It only supports the orderBy option, the default value is DESC. """ _filters = {} - aux = list(reversed(str(orderby).split('.'))) - for split in aux: + reverse_filter = list(reversed(orderby.split('.'))) + for keyword in reverse_filter: _aux_filter = {} - if str(split).__contains__('='): - _aux_filter[str(split).split('=')[0]] = query_filter_orderby(str(split).split('=')[1]) + if '=' in keyword: + _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter - elif split == list(aux)[0]: - _aux_filter[split] = query_filter_orderby('DESC') + elif keyword == list(reverse_filter)[0]: + _aux_filter[keyword] = query_filter_orderby('DESC') else: - _aux_filter[split] = _filters + _aux_filter[keyword] = _filters _filters = _aux_filter return _filters diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index b98998f29..d00eb4aaf 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -301,7 +301,9 @@ def test_json_filter(self): def test_call_api_orderBy(self): result = self.run_command(['call-api', 'Account', 'getVirtualGuests', - '--orderBy', 'virtualGuests.id=DESC']) + '--orderBy', 'virtualGuests.id=DESC', + '--mask=virtualGuests.typeId,maxCpu', + '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', @@ -311,4 +313,6 @@ def test_call_api_orderBy(self): 'operation': 'orderBy', 'options': [{ 'name': 'sort', - 'value': ['DESC']}]}}}) + 'value': ['DESC']}]}, + 'typeId': {'operation': 1}} + }) From fbe3b0387c22e0e24cce5424520dc8aa84d73b63 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:33:18 -0500 Subject: [PATCH 0632/1582] #1474 replaced using 'mock' with 'unitest.mock' --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/core_tests.py | 2 +- tests/CLI/environment_tests.py | 2 +- tests/CLI/helper_tests.py | 2 +- tests/CLI/modules/autoscale_tests.py | 2 +- tests/CLI/modules/block_tests.py | 2 +- tests/CLI/modules/config_tests.py | 2 +- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/dns_tests.py | 2 +- tests/CLI/modules/file_tests.py | 2 +- tests/CLI/modules/firewall_tests.py | 2 +- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/loadbal_tests.py | 2 +- tests/CLI/modules/object_storage_tests.py | 2 +- tests/CLI/modules/securitygroup_tests.py | 2 +- tests/CLI/modules/server_tests.py | 2 +- tests/CLI/modules/sshkey_tests.py | 2 +- tests/CLI/modules/ssl_tests.py | 2 +- tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/tag_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 2 +- tests/CLI/modules/user_tests.py | 2 +- tests/CLI/modules/vlan_tests.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 2 +- tests/CLI/modules/vs/vs_placement_tests.py | 2 +- tests/CLI/modules/vs/vs_tests.py | 2 +- tests/api_tests.py | 2 +- tests/config_tests.py | 2 +- tests/decoration_tests.py | 2 +- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/hardware_tests.py | 2 +- tests/managers/network_tests.py | 2 +- tests/managers/ordering_tests.py | 2 +- tests/managers/user_tests.py | 2 +- tests/managers/vs/vs_capacity_tests.py | 2 +- tests/managers/vs/vs_order_tests.py | 2 +- tests/managers/vs/vs_placement_tests.py | 2 +- tests/managers/vs/vs_tests.py | 2 +- tests/managers/vs/vs_waiting_for_ready_tests.py | 2 +- tests/transport_tests.py | 2 +- 40 files changed, 40 insertions(+), 40 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index 563b02494..a9054e3bb 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -11,7 +11,7 @@ import unittest from click import testing -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/core_tests.py b/tests/CLI/core_tests.py index f230a3513..e8720514d 100644 --- a/tests/CLI/core_tests.py +++ b/tests/CLI/core_tests.py @@ -8,7 +8,7 @@ import logging import click -import mock +from unittest import mock as mock from requests.models import Response import SoftLayer diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index fa90ba1db..ed393afde 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -6,7 +6,7 @@ """ import click -import mock +from unittest import mock as mock from SoftLayer.CLI import environment from SoftLayer import testing diff --git a/tests/CLI/helper_tests.py b/tests/CLI/helper_tests.py index c22278c34..e3a6218c2 100644 --- a/tests/CLI/helper_tests.py +++ b/tests/CLI/helper_tests.py @@ -11,7 +11,7 @@ import tempfile import click -import mock +from unittest import mock as mock from SoftLayer.CLI import core from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6d0e543da..6a1e9e37c 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,7 +7,7 @@ Tests for the autoscale cli command """ -import mock +from unittest import mock as mock import sys from SoftLayer import fixtures diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index 83cba03d0..c38a7544d 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -10,7 +10,7 @@ import json -import mock +from unittest import mock as mock class BlockTests(testing.TestCase): diff --git a/tests/CLI/modules/config_tests.py b/tests/CLI/modules/config_tests.py index 5fe917c1c..ef16edf38 100644 --- a/tests/CLI/modules/config_tests.py +++ b/tests/CLI/modules/config_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import auth diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index 077c3f033..a3199a744 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/dns_tests.py b/tests/CLI/modules/dns_tests.py index 82403d1a9..8fb8714f0 100644 --- a/tests/CLI/modules/dns_tests.py +++ b/tests/CLI/modules/dns_tests.py @@ -8,7 +8,7 @@ import os.path import sys -import mock +from unittest import mock as mock from SoftLayer.CLI.dns import zone_import from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 1bfe58e16..442ca067d 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class FileTests(testing.TestCase): diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 7362f1557..d80605d81 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 6f2ee40d5..97633c0b6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index b2da4c374..8576a8292 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -3,7 +3,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer.CLI.exceptions import ArgumentError diff --git a/tests/CLI/modules/object_storage_tests.py b/tests/CLI/modules/object_storage_tests.py index 2e843906d..b5a219f62 100644 --- a/tests/CLI/modules/object_storage_tests.py +++ b/tests/CLI/modules/object_storage_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ import json -from unittest import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/securitygroup_tests.py b/tests/CLI/modules/securitygroup_tests.py index b6801fcc8..2c930df71 100644 --- a/tests/CLI/modules/securitygroup_tests.py +++ b/tests/CLI/modules/securitygroup_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import testing diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index de7ccd95e..198160000 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,7 +8,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys from SoftLayer.CLI import exceptions diff --git a/tests/CLI/modules/sshkey_tests.py b/tests/CLI/modules/sshkey_tests.py index 253309c08..61260a2f0 100644 --- a/tests/CLI/modules/sshkey_tests.py +++ b/tests/CLI/modules/sshkey_tests.py @@ -9,7 +9,7 @@ import sys import tempfile -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import testing diff --git a/tests/CLI/modules/ssl_tests.py b/tests/CLI/modules/ssl_tests.py index 79b04df41..2bcdff5ca 100644 --- a/tests/CLI/modules/ssl_tests.py +++ b/tests/CLI/modules/ssl_tests.py @@ -7,7 +7,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock class SslTests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 57e7dbbb4..6d7a9bbaa 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -9,7 +9,7 @@ from SoftLayer import testing import json -import mock +from unittest import mock as mock import SoftLayer diff --git a/tests/CLI/modules/tag_tests.py b/tests/CLI/modules/tag_tests.py index b2e29721e..364201181 100644 --- a/tests/CLI/modules/tag_tests.py +++ b/tests/CLI/modules/tag_tests.py @@ -4,7 +4,7 @@ Tests for the user cli command """ -import mock +from unittest import mock as mock from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import testing diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 7b2363eab..92bd848d6 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ import json -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting diff --git a/tests/CLI/modules/user_tests.py b/tests/CLI/modules/user_tests.py index 2f4c1c978..a11a94838 100644 --- a/tests/CLI/modules/user_tests.py +++ b/tests/CLI/modules/user_tests.py @@ -8,7 +8,7 @@ import sys import unittest -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ee606f513..73c1fab97 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 2ad0f8647..9d644aabd 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import tempfile diff --git a/tests/CLI/modules/vs/vs_placement_tests.py b/tests/CLI/modules/vs/vs_placement_tests.py index 3b716a6cd..aadf20426 100644 --- a/tests/CLI/modules/vs/vs_placement_tests.py +++ b/tests/CLI/modules/vs/vs_placement_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import testing diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index de3a310b9..608b9dd6f 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -7,7 +7,7 @@ import json import sys -import mock +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Virtual_Guest as SoftLayer_Virtual_Guest diff --git a/tests/api_tests.py b/tests/api_tests.py index 39f596b3c..a83246858 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer import SoftLayer.API diff --git a/tests/config_tests.py b/tests/config_tests.py index f6adb1be6..c4abdb032 100644 --- a/tests/config_tests.py +++ b/tests/config_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer import config from SoftLayer import testing diff --git a/tests/decoration_tests.py b/tests/decoration_tests.py index 9d230671c..d7a39d757 100644 --- a/tests/decoration_tests.py +++ b/tests/decoration_tests.py @@ -6,7 +6,7 @@ """ import logging -import mock +from unittest import mock as mock from SoftLayer.decoration import retry from SoftLayer import exceptions diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index 6888db3ce..ea0efeb42 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 9f48ad2aa..3fbdf5740 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -6,7 +6,7 @@ """ import copy -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 80d054f9d..735492b9b 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import sys import unittest diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 5f88d59d5..53995264d 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/user_tests.py b/tests/managers/user_tests.py index 61f5b4d0a..5ea4d2696 100644 --- a/tests/managers/user_tests.py +++ b/tests/managers/user_tests.py @@ -5,7 +5,7 @@ """ import datetime -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_capacity_tests.py b/tests/managers/vs/vs_capacity_tests.py index c6aad9f56..6fa6599e8 100644 --- a/tests/managers/vs/vs_capacity_tests.py +++ b/tests/managers/vs/vs_capacity_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/managers/vs/vs_order_tests.py b/tests/managers/vs/vs_order_tests.py index 7b54f5450..ae77df2cd 100644 --- a/tests/managers/vs/vs_order_tests.py +++ b/tests/managers/vs/vs_order_tests.py @@ -6,7 +6,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_placement_tests.py b/tests/managers/vs/vs_placement_tests.py index b492f69bf..7a6a69457 100644 --- a/tests/managers/vs/vs_placement_tests.py +++ b/tests/managers/vs/vs_placement_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock from SoftLayer.managers.vs_placement import PlacementManager from SoftLayer import testing diff --git a/tests/managers/vs/vs_tests.py b/tests/managers/vs/vs_tests.py index d5202bbe8..976a66fd8 100644 --- a/tests/managers/vs/vs_tests.py +++ b/tests/managers/vs/vs_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/managers/vs/vs_waiting_for_ready_tests.py b/tests/managers/vs/vs_waiting_for_ready_tests.py index 4308bd55d..08b3071c6 100644 --- a/tests/managers/vs/vs_waiting_for_ready_tests.py +++ b/tests/managers/vs/vs_waiting_for_ready_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -import mock +from unittest import mock as mock import SoftLayer from SoftLayer import exceptions diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 27f892098..854ee6b2e 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -8,7 +8,7 @@ import warnings import json -import mock +from unittest import mock as mock import pytest import requests From f8e88bc83190548020479acd4f046c1214a00a27 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:34:24 -0500 Subject: [PATCH 0633/1582] autopep8 changes --- tests/CLI/modules/globalip_tests.py | 2 +- tests/CLI/modules/server_tests.py | 36 +++++++++++------------ tests/CLI/modules/subnet_tests.py | 2 +- tests/CLI/modules/ticket_tests.py | 1 + tests/CLI/modules/vs/vs_create_tests.py | 34 ++++++++++----------- tests/CLI/modules/vs/vs_tests.py | 39 +++++++++++++------------ tests/managers/hardware_tests.py | 8 ++--- tests/managers/ordering_tests.py | 9 +++--- 8 files changed, 67 insertions(+), 64 deletions(-) diff --git a/tests/CLI/modules/globalip_tests.py b/tests/CLI/modules/globalip_tests.py index 97633c0b6..e12b7c3f6 100644 --- a/tests/CLI/modules/globalip_tests.py +++ b/tests/CLI/modules/globalip_tests.py @@ -79,7 +79,7 @@ def test_create(self, confirm_mock): { "item": "Total monthly cost", "cost": "2.00" - }]) + }]) def test_ip_unassign(self): result = self.run_command(['globalip', 'unassign', '1']) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 198160000..d39af4571 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -692,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -747,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index 6d7a9bbaa..b73529a4f 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -173,7 +173,7 @@ def test_lookup(self): "netmask": "255.255.255.192", "gateway": "10.47.16.129", "type": "PRIMARY" - }}) + }}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_cancel(self, confirm_mock): diff --git a/tests/CLI/modules/ticket_tests.py b/tests/CLI/modules/ticket_tests.py index 92bd848d6..4fbdbff0c 100644 --- a/tests/CLI/modules/ticket_tests.py +++ b/tests/CLI/modules/ticket_tests.py @@ -15,6 +15,7 @@ class FakeTTY(): """A fake object to fake STD input""" + def __init__(self, isatty=False, read="Default Output"): """Sets isatty and read""" self._isatty = isatty diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 9d644aabd..52625d337 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -113,26 +113,26 @@ def test_create_by_router(self, confirm_mock): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') args = ({ - 'startCpus': 2, - 'maxMemory': 1024, - 'hostname': 'host', - 'domain': 'example.com', - 'localDiskFlag': True, - 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': {'bootMode': None}, - 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': {'name': 'dal05'}, - 'primaryBackendNetworkComponent': { + 'startCpus': 2, + 'maxMemory': 1024, + 'hostname': 'host', + 'domain': 'example.com', + 'localDiskFlag': True, + 'hourlyBillingFlag': True, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'operatingSystemReferenceCode': 'UBUNTU_LATEST', + 'datacenter': {'name': 'dal05'}, + 'primaryBackendNetworkComponent': { 'router': { 'id': 577940 } - }, - 'primaryNetworkComponent': { - 'router': { - 'id': 1639255 - } - } - },) + }, + 'primaryNetworkComponent': { + 'router': { + 'id': 1639255 + } + } + },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) diff --git a/tests/CLI/modules/vs/vs_tests.py b/tests/CLI/modules/vs/vs_tests.py index 608b9dd6f..97dc52ea4 100644 --- a/tests/CLI/modules/vs/vs_tests.py +++ b/tests/CLI/modules/vs/vs_tests.py @@ -322,7 +322,8 @@ def test_create_options_prices(self): self.assert_no_fail(result) def test_create_options_prices_location(self): - result = self.run_command(['vs', 'create-options', '--prices', 'dal13', '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) + result = self.run_command(['vs', 'create-options', '--prices', 'dal13', + '--vsi-type', 'TRANSIENT_CLOUD_SERVER']) self.assert_no_fail(result) @mock.patch('SoftLayer.CLI.formatting.confirm') @@ -344,19 +345,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'vs-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.240.2', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'vs-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.240.2', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '2', - 'domainId': 123456, - 'data': 'vs-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '2', + 'domainId': 123456, + 'data': 'vs-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['vs', 'dns-sync', '100']) @@ -399,12 +400,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'vs-test1', - 'domainId': 12345, - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'vs-test1', + 'domainId': 12345, + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) guest.return_value = test_guest result = self.run_command(['vs', 'dns-sync', '--aaaa-record', '100']) self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 3fbdf5740..fa5459cd1 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py index 53995264d..b25c42494 100644 --- a/tests/managers/ordering_tests.py +++ b/tests/managers/ordering_tests.py @@ -744,7 +744,7 @@ def test_get_item_capacity_core(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['GUEST_CORE_1_DEDICATED', 'OS_RHEL_7_X_LAMP_64_BIT']) @@ -761,7 +761,7 @@ def test_get_item_capacity_storage(self): "capacity": "1", "id": 10201, "keyName": "READHEAVY_TIER", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['READHEAVY_TIER', 'STORAGE_SPACE_FOR_2_IOPS_PER_GB']) @@ -779,7 +779,7 @@ def test_get_item_capacity_intel(self): "capacity": "1", "id": 10201, "keyName": "GUEST_CORE_1_DEDICATED", - }] + }] item_capacity = self.ordering.get_item_capacity(items, ['INTEL_XEON_2690_2_60', 'BANDWIDTH_20000_GB']) @@ -848,7 +848,8 @@ def test_resolve_location_name_invalid(self): self.assertIn("Invalid location", str(exc)) def test_resolve_location_name_not_exist(self): - exc = self.assertRaises(exceptions.SoftLayerError, self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") + exc = self.assertRaises(exceptions.SoftLayerError, + self.ordering.resolve_location_name, "UNKNOWN_LOCATION_TEST") self.assertIn("does not exist", str(exc)) # https://github.com/softlayer/softlayer-python/issues/1425 From 09f86f435277bdee5379fe2586a3e97905d850e2 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 14:54:02 -0500 Subject: [PATCH 0634/1582] fixing import order stuff --- SoftLayer/testing/__init__.py | 2 +- tests/CLI/modules/autoscale_tests.py | 3 ++- tests/CLI/modules/dedicatedhost_tests.py | 2 +- tests/CLI/modules/server_tests.py | 7 +++---- tests/CLI/modules/subnet_tests.py | 8 ++++---- tests/CLI/modules/vs/vs_create_tests.py | 3 ++- tests/managers/dedicated_host_tests.py | 2 +- tests/managers/ipsec_tests.py | 3 +-- tests/managers/network_tests.py | 3 ++- tests/transport_tests.py | 4 ++-- 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py index a9054e3bb..6eff9851c 100644 --- a/SoftLayer/testing/__init__.py +++ b/SoftLayer/testing/__init__.py @@ -9,9 +9,9 @@ import logging import os.path import unittest +from unittest import mock as mock from click import testing -from unittest import mock as mock import SoftLayer from SoftLayer.CLI import core diff --git a/tests/CLI/modules/autoscale_tests.py b/tests/CLI/modules/autoscale_tests.py index 6a1e9e37c..cb3cdfdb9 100644 --- a/tests/CLI/modules/autoscale_tests.py +++ b/tests/CLI/modules/autoscale_tests.py @@ -7,8 +7,9 @@ Tests for the autoscale cli command """ -from unittest import mock as mock + import sys +from unittest import mock as mock from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/CLI/modules/dedicatedhost_tests.py b/tests/CLI/modules/dedicatedhost_tests.py index a3199a744..a015fa70d 100644 --- a/tests/CLI/modules/dedicatedhost_tests.py +++ b/tests/CLI/modules/dedicatedhost_tests.py @@ -6,8 +6,8 @@ """ import json from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer.CLI import exceptions from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer.fixtures import SoftLayer_Virtual_DedicatedHost diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index d39af4571..e6cb2b18a 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -8,16 +8,15 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock +import json import sys +import tempfile +from unittest import mock as mock from SoftLayer.CLI import exceptions from SoftLayer import SoftLayerError from SoftLayer import testing -import json -import tempfile - class ServerCLITests(testing.TestCase): diff --git a/tests/CLI/modules/subnet_tests.py b/tests/CLI/modules/subnet_tests.py index b73529a4f..65a4cc5c8 100644 --- a/tests/CLI/modules/subnet_tests.py +++ b/tests/CLI/modules/subnet_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.fixtures import SoftLayer_Product_Order -from SoftLayer.fixtures import SoftLayer_Product_Package -from SoftLayer import testing - import json from unittest import mock as mock + import SoftLayer +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing class SubnetTests(testing.TestCase): diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index 52625d337..e9bb2fd74 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import tempfile +from unittest import mock as mock from SoftLayer.fixtures import SoftLayer_Product_Package as SoftLayer_Product_Package from SoftLayer import testing diff --git a/tests/managers/dedicated_host_tests.py b/tests/managers/dedicated_host_tests.py index ea0efeb42..afe3df3a2 100644 --- a/tests/managers/dedicated_host_tests.py +++ b/tests/managers/dedicated_host_tests.py @@ -5,8 +5,8 @@ :license: MIT, see LICENSE for more details. """ from unittest import mock as mock -import SoftLayer +import SoftLayer from SoftLayer import exceptions from SoftLayer import fixtures from SoftLayer import testing diff --git a/tests/managers/ipsec_tests.py b/tests/managers/ipsec_tests.py index aaebc9f7f..f88e33ed5 100644 --- a/tests/managers/ipsec_tests.py +++ b/tests/managers/ipsec_tests.py @@ -4,8 +4,7 @@ :license: MIT, see LICENSE for more details. """ - -from mock import MagicMock +from unittest.mock import MagicMock as MagicMock import SoftLayer from SoftLayer.exceptions import SoftLayerAPIError diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 735492b9b..2463ed999 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -4,9 +4,10 @@ :license: MIT, see LICENSE for more details. """ -from unittest import mock as mock + import sys import unittest +from unittest import mock as mock import SoftLayer from SoftLayer import fixtures diff --git a/tests/transport_tests.py b/tests/transport_tests.py index 854ee6b2e..ca3dcc73b 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ import io -import warnings - import json from unittest import mock as mock +import warnings + import pytest import requests From dceab111688fe6bccd3b04a96d1db6d400829c41 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:17:57 -0500 Subject: [PATCH 0635/1582] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b1fa2c870..73b9a0007 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py35,py36,py37,py38,py39,pypy3,analysis,coverage,docs +envlist = py36,py37,py38,py39,pypy3,analysis,coverage,docs [flake8] From 1039f158b13fd5a9f5725e68285e5e5b651188cf Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 23 Apr 2021 15:28:35 -0500 Subject: [PATCH 0636/1582] dropping support for py3.5 as it is EOL https://www.python.org/downloads/release/python-3510/ --- .github/workflows/tests.yml | 2 +- setup.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7bc787791..7d6d35a67 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5,3.6,3.7,3.8,3.9] + python-version: [3.6,3.7,3.8,3.9] steps: - uses: actions/checkout@v2 diff --git a/setup.py b/setup.py index a09cca0a4..c040d9ca7 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,6 @@ 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', From fda7e0a4371f5f245f79512e84c76e8d8f47fdcd Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:05:23 -0400 Subject: [PATCH 0637/1582] fix the tox tool --- tests/CLI/modules/call_api_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index d00eb4aaf..a22597488 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -305,8 +305,7 @@ def test_call_api_orderBy(self): '--mask=virtualGuests.typeId,maxCpu', '-f', 'virtualGuests.typeId=1']) self.assert_no_fail(result) - self.assert_called_with('SoftLayer_Account', - 'getVirtualGuests', + self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': {'id': { From 5ca3bdee066028def3d926d0a78bafdf527c910f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:27:00 -0400 Subject: [PATCH 0638/1582] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..23b43e936 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,6 +35,7 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) + table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From c8893fd6aa20ec8efa44cf712fc210de8971c7ee Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Apr 2021 17:29:35 -0400 Subject: [PATCH 0639/1582] update and fix tox tool --- SoftLayer/CLI/firewall/detail.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index 23b43e936..e3b61e088 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -35,7 +35,6 @@ def cli(env, identifier): rules = mgr.get_dedicated_fwl_rules(firewall_id) else: rules = mgr.get_standard_fwl_rules(firewall_id) - table.add_row(['rules', get_rules_table(rules)]) env.fout(table) From 8de6d4a51d8c83743054290ce4954d8543c1f0ef Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:36:52 -0500 Subject: [PATCH 0640/1582] moved the test_pypi workflow to only trigger on test-pypi branch as we don't have access to the test environment yet --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 5e7c6b683..f3b39abf9 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ test-pypi ] jobs: build-n-publish: From bdade312228af1c17a3ca0a4440648b35cc8e2aa Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:37:09 -0500 Subject: [PATCH 0641/1582] Changelog for v5.9.4 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ab66753..f55340957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Change Log +## [5.9.4] - 2021-04-27 +https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 + +#### New Commands +- `slcli hw authorize-storage` #1439 +- `slcli order quote-save` #1451 + + +#### Improvements + +- Refactored managers.ordering_manager.verify_quote() to work better with the REST endpoing #1430 +- Add routers for each DC in slcli hw create-options #1432 +- Add preset datatype in slcli virtual detail #1435 +- Add upgrade option to slcli hw. #1437 +- Ibmcloud authentication support #1315 / #1447 + + `slcli config setup --ibmid` + + `slcli config setup --sso` + + `slcli config setup --cloud_key` + + `slcli config setup --classic_key` +- Refactor slcli hw detail prices. #1443 +- Updated contributing guide #1458 +- Add the Hardware components on "slcli hardware detail" #1452 +- Add billing and lastTransaction on hardware detail #1446 +- Forced reserved capacity guests to be monthly #1454 +- Removing the rwhois commands #1456 +- Added automation to publish to test-pypi #1467 +- Updating author_email to SLDN distro list #1469 +- Add the option to add and upgrade the hw disk. #1455 +- Added a utility to merge objectFilters, #1468 +- Fixes shift+ins when pasteing into a password field for windows users. #1460 +- Add Billing and lastTransaction on slcli virtual detail #1466 +- Fixing 'import mock' pylint issues #1476 + ## [5.9.3] - 2021-03-03 https://github.com/softlayer/softlayer-python/compare/v5.9.2...v5.9.3 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 71c52be75..cec2831e0 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.3' +VERSION = 'v5.9.4' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c040d9ca7..48c1e4e6c 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.3', + version='5.9.4', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 486b0d068911c5d3214874796261fc298e2ce680 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 27 Apr 2021 10:52:37 -0500 Subject: [PATCH 0642/1582] fixed a pylint issue --- SoftLayer/CLI/sshkey/add.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index a3a3c6ccb..d80330d7c 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,9 +33,9 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - key_file = open(path.expanduser(in_file), 'rU') - key_text = key_file.read().strip() - key_file.close() + with open(path.expanduser(in_file), 'rU') as key_file: + key_text = key_file.read().strip() + key_file.close() mgr = SoftLayer.SshKeyManager(env.client) result = mgr.add_key(key_text, label, note) From 9a8fcf8079b73cd8883e6f57eaf40543eac4c499 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 27 Apr 2021 12:37:02 -0400 Subject: [PATCH 0643/1582] #1449 add image detail transaction data --- SoftLayer/CLI/image/__init__.py | 3 ++- SoftLayer/CLI/image/detail.py | 46 +++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/image/__init__.py b/SoftLayer/CLI/image/__init__.py index b0734db99..17d836625 100644 --- a/SoftLayer/CLI/image/__init__.py +++ b/SoftLayer/CLI/image/__init__.py @@ -5,7 +5,8 @@ MASK = ('id,accountId,name,globalIdentifier,parentId,publicFlag,flexImageFlag,' 'imageType') -DETAIL_MASK = MASK + (',children[id,blockDevicesDiskSpaceTotal,datacenter],' +DETAIL_MASK = MASK + (',firstChild,children[id,blockDevicesDiskSpaceTotal,datacenter,' + 'transaction[transactionGroup,transactionStatus]],' 'note,createDate,status,transaction') PUBLIC_TYPE = formatting.FormattedItem('PUBLIC', 'Public') PRIVATE_TYPE = formatting.FormattedItem('PRIVATE', 'Private') diff --git a/SoftLayer/CLI/image/detail.py b/SoftLayer/CLI/image/detail.py index 5bba1beac..865f95a7d 100644 --- a/SoftLayer/CLI/image/detail.py +++ b/SoftLayer/CLI/image/detail.py @@ -19,14 +19,10 @@ def cli(env, identifier): image_mgr = SoftLayer.ImageManager(env.client) image_id = helpers.resolve_id(image_mgr.resolve_ids, identifier, 'image') - image = image_mgr.get_image(image_id, mask=image_mod.DETAIL_MASK) - disk_space = 0 - datacenters = [] - for child in image.get('children'): - disk_space = int(child.get('blockDevicesDiskSpaceTotal', 0)) - if child.get('datacenter'): - datacenters.append(utils.lookup(child, 'datacenter', 'name')) + + children_images = image.get('children') + total_size = utils.lookup(image, 'firstChild', 'blockDevicesDiskSpaceTotal') or 0 table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' @@ -40,9 +36,10 @@ def cli(env, identifier): utils.lookup(image, 'status', 'keyname'), utils.lookup(image, 'status', 'name'), )]) + table.add_row([ 'active_transaction', - formatting.transaction_status(image.get('transaction')), + formatting.listing(_get_transaction_groups(children_images), separator=','), ]) table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['visibility', @@ -56,8 +53,35 @@ def cli(env, identifier): table.add_row(['flex', image.get('flexImageFlag')]) table.add_row(['note', image.get('note')]) table.add_row(['created', image.get('createDate')]) - table.add_row(['disk_space', formatting.b_to_gb(disk_space)]) - table.add_row(['datacenters', formatting.listing(sorted(datacenters), - separator=',')]) + table.add_row(['total_size', formatting.b_to_gb(total_size)]) + table.add_row(['datacenters', _get_datacenter_table(children_images)]) env.fout(table) + + +def _get_datacenter_table(children_images): + """Returns image details as datacenter, size, and transaction within a formatting table. + + :param children_images: A list of images. + """ + table_datacenter = formatting.Table(['DC', 'size', 'transaction']) + for child in children_images: + table_datacenter.add_row([ + utils.lookup(child, 'datacenter', 'name'), + formatting.b_to_gb(child.get('blockDevicesDiskSpaceTotal', 0)), + formatting.transaction_status(child.get('transaction')) + ]) + + return table_datacenter + + +def _get_transaction_groups(children_images): + """Returns a Set of transaction groups. + + :param children_images: A list of images. + """ + transactions = set() + for child in children_images: + transactions.add(utils.lookup(child, 'transaction', 'transactionGroup', 'name')) + + return transactions From 661a2258de89c0608abef252c713707934c36192 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 16:37:45 -0400 Subject: [PATCH 0644/1582] Fix to Christopher code review comments --- SoftLayer/managers/firewall.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 633eddfd8..44eef25ab 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -297,7 +297,7 @@ def get_instance(self, firewall_id, mask=None): :param integer firewall_id: the instance ID of the standard firewall """ if not mask: - mask = ('mask[datacenter,networkVlan]') + mask = 'mask[datacenter,networkVlan]' svc = self.client['Network_Vlan_Firewall'] From aece6185968a6754636476a35b067ca269e3a578 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:27:16 -0400 Subject: [PATCH 0645/1582] Fix to Christopher code review comments --- tests/CLI/modules/firewall_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CLI/modules/firewall_tests.py b/tests/CLI/modules/firewall_tests.py index 59c1a5a1a..f248b16f1 100644 --- a/tests/CLI/modules/firewall_tests.py +++ b/tests/CLI/modules/firewall_tests.py @@ -58,6 +58,8 @@ def test_add_server(self, confirm_mock): def test_detail(self): result = self.run_command(['firewall', 'detail', 'vlan:1234']) self.assert_no_fail(result) + json_result = json.loads(result.output) + self.assertEqual(json_result['rules'][0]['action'], 'permit') self.assertEqual(json.loads(result.output), {'datacenter': 'Amsterdam 1', 'id': 3130, From 66bc9adb655f912082dd9a45ac8ef9327abd2d63 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 17:42:35 -0400 Subject: [PATCH 0646/1582] Fix to Christopher code review comments --- SoftLayer/CLI/firewall/detail.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index e3b61e088..afdf13b53 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -25,11 +25,11 @@ def cli(env, identifier): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', result['id']]) - table.add_row(['primaryIpAddress', result['primaryIpAddress']]) - table.add_row(['datacenter', result['datacenter']['longName']]) - table.add_row(['networkVlan', result['networkVlan']['name']]) - table.add_row(['networkVlaniD', result['networkVlan']['id']]) + table.add_row(['id', utils.lookup(result, 'id')]) + table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From d53dc43b35207cf7e1081e3d9fa7c7fb6221fdef Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 29 Apr 2021 18:02:07 -0400 Subject: [PATCH 0647/1582] Fix to team code review comments --- tests/CLI/modules/call_api_tests.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/CLI/modules/call_api_tests.py b/tests/CLI/modules/call_api_tests.py index a22597488..03c861d2a 100644 --- a/tests/CLI/modules/call_api_tests.py +++ b/tests/CLI/modules/call_api_tests.py @@ -308,10 +308,11 @@ def test_call_api_orderBy(self): self.assert_called_with('SoftLayer_Account', 'getVirtualGuests', filter={ 'virtualGuests': - {'id': { - 'operation': 'orderBy', - 'options': [{ - 'name': 'sort', - 'value': ['DESC']}]}, + {'id': + { + 'operation': 'orderBy', + 'options': [{ + 'name': 'sort', + 'value': ['DESC']}]}, 'typeId': {'operation': 1}} }) From 39b3b2a758d9dbd1eb81612b6d443dc13d0d5d32 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 30 Apr 2021 08:59:21 -0400 Subject: [PATCH 0648/1582] Fix to team code review comments --- SoftLayer/CLI/firewall/detail.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/firewall/detail.py b/SoftLayer/CLI/firewall/detail.py index afdf13b53..647715a3d 100644 --- a/SoftLayer/CLI/firewall/detail.py +++ b/SoftLayer/CLI/firewall/detail.py @@ -19,17 +19,17 @@ def cli(env, identifier): mgr = SoftLayer.FirewallManager(env.client) firewall_type, firewall_id = firewall.parse_id(identifier) - result = mgr.get_instance(firewall_id) + _firewall = mgr.get_instance(firewall_id) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', utils.lookup(result, 'id')]) - table.add_row(['primaryIpAddress', utils.lookup(result, 'primaryIpAddress')]) - table.add_row(['datacenter', utils.lookup(result, 'datacenter', 'longName')]) - table.add_row(['networkVlan', utils.lookup(result, 'networkVlan', 'name')]) - table.add_row(['networkVlaniD', utils.lookup(result, 'networkVlan', 'id')]) + table.add_row(['id', _firewall.get('id')]) + table.add_row(['primaryIpAddress', _firewall.get('primaryIpAddress')]) + table.add_row(['datacenter', utils.lookup(_firewall, 'datacenter', 'longName')]) + table.add_row(['networkVlan', utils.lookup(_firewall, 'networkVlan', 'name')]) + table.add_row(['networkVlaniD', utils.lookup(_firewall, 'networkVlan', 'id')]) if firewall_type == 'vlan': rules = mgr.get_dedicated_fwl_rules(firewall_id) From bdccfa963a07968b53dd72c26ab2405fbafb848a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 09:53:58 -0400 Subject: [PATCH 0649/1582] add new email feature --- SoftLayer/CLI/email/__init__.py | 0 SoftLayer/CLI/email/detail.py | 72 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 + SoftLayer/fixtures/SoftLayer_Account.py | 24 +++++++ ...Network_Message_Delivery_Email_Sendgrid.py | 27 +++++++ SoftLayer/managers/email.py | 47 ++++++++++++ docs/api/managers/email.rst | 5 ++ docs/cli/email.rst | 9 +++ tests/CLI/modules/email_tests.py | 15 ++++ tests/managers/email_tests.py | 26 +++++++ 10 files changed, 228 insertions(+) create mode 100644 SoftLayer/CLI/email/__init__.py create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py create mode 100644 SoftLayer/managers/email.py create mode 100644 docs/api/managers/email.rst create mode 100644 docs/cli/email.rst create mode 100644 tests/CLI/modules/email_tests.py create mode 100644 tests/managers/email_tests.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..44a4bbbdb --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,72 @@ +"""Get details for an image.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """""" + manager = AccountManager(env.client) + email_manager = EmailManager(env.client) + result = manager.get_Network_Message_Delivery_Accounts() + + table = formatting.KeyValueTable(['name', 'value']) + + table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) + table_information.align['id'] = 'r' + table_information.align['username'] = 'l' + + for email in result: + # print(email['id']) + table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), + utils.lookup(email, 'type', 'description'), + utils.lookup(email, 'vendor', 'keyName')]) + + overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id'), + ["requests", "delivered", "opens", "clicks", "bounds"], + True, True, True, 6) + + table.add_row(['email information', table_information]) + table.add_row(['email overview', overview_table]) + for statistic in statistics: + table.add_row(['statistics', _build_statistics_table(statistic)]) + + env.fout(table) + + +def _build_overview_table(email_overview): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) + table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) + table.add_row(['package', email_overview.get('package')]) + table.add_row(['reputation', email_overview.get('reputation')]) + table.add_row(['requests', email_overview.get('requests')]) + + return table + + +def _build_statistics_table(statistics): + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['delivered', statistics.get('delivered')]) + table.add_row(['requests', statistics.get('requests')]) + table.add_row(['bounces', statistics.get('bounces')]) + table.add_row(['opens', statistics.get('opens')]) + table.add_row(['clicks', statistics.get('clicks')]) + table.add_row(['spam Reports', statistics.get('spamReports')]) + + return table diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..0c6f322b7 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -119,6 +119,9 @@ ('block:volume-convert', 'SoftLayer.CLI.block.convert:cli'), ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), + ('email', 'SoftLayer.CLI.email'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4261c32a7..4cfafa30f 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1076,3 +1076,27 @@ "username": "SL01SEV1234567_111" } ] + +getNetworkMessageDeliveryAccounts = [ + { + "accountId": 147258, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "typeId": 21, + "username": "test_CLI@ie.ibm.com", + "vendorId": 1, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "test_CLI@ie.ibm.com", + "smtpAccess": "1" + } +] diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py new file mode 100644 index 000000000..94fd169fd --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -0,0 +1,27 @@ +getAccountOverview = { + "creditsAllowed": 25000, + "creditsOverage": 0, + "creditsRemain": 25000, + "creditsUsed": 0, + "package": "Free Package", + "reputation": 100, + "requests": 56 +} + +getStatistics = [{ + "blocks": 0, + "bounces": 0, + "clicks": 0, + "date": "2021-04-28", + "delivered": 0, + "invalidEmail": 0, + "opens": 0, + "repeatBounces": 0, + "repeatSpamReports": 0, + "repeatUnsubscribes": 0, + "requests": 0, + "spamReports": 0, + "uniqueClicks": 0, + "uniqueOpens": 0, + "unsubscribes": 0 +}] diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py new file mode 100644 index 000000000..5b5f49e00 --- /dev/null +++ b/SoftLayer/managers/email.py @@ -0,0 +1,47 @@ +""" + SoftLayer.account + ~~~~~~~~~~~~~~~~~~~~~~~ + Account manager + + :license: MIT, see License for more details. +""" + +from SoftLayer import utils + + +# Invalid names are ignored due to long method names and short argument names +# pylint: disable=invalid-name, no-self-use + + +class EmailManager(utils.IdentifierMixin, object): + """Common functions for getting information from the Account service + + :param SoftLayer.API.BaseClient client: the client instance + """ + + def __init__(self, client): + self.client = client + + def get_AccountOverview(self, identifier): + """Gets all the Network Message Delivery Account Overview + + :returns: Network Message Delivery Account overview + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview', id=identifier) + + def get_statistics(self, identifier, selectedStatistics, + startDate, endDate, aggregatesOnly, days): + """Gets statistics Network Message Delivery Account + + :returns: statistics Network Message Delivery Account + """ + body = [selectedStatistics, + startDate, + endDate, + aggregatesOnly, + days + ] + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics', id=identifier, *body) diff --git a/docs/api/managers/email.rst b/docs/api/managers/email.rst new file mode 100644 index 000000000..45d839c16 --- /dev/null +++ b/docs/api/managers/email.rst @@ -0,0 +1,5 @@ +.. _email: + +.. automodule:: SoftLayer.managers.email + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/email.rst b/docs/cli/email.rst new file mode 100644 index 000000000..67d5319db --- /dev/null +++ b/docs/cli/email.rst @@ -0,0 +1,9 @@ +.. _cli_email: + +Email Commands +================= + + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py new file mode 100644 index 000000000..db085efbf --- /dev/null +++ b/tests/CLI/modules/email_tests.py @@ -0,0 +1,15 @@ +""" + SoftLayer.tests.CLI.modules.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the user cli command +""" +from SoftLayer import testing + + +class EmailCLITests(testing.TestCase): + + def test_detail(self): + result = self.run_command(['email', 'detail']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py new file mode 100644 index 000000000..b573326fa --- /dev/null +++ b/tests/managers/email_tests.py @@ -0,0 +1,26 @@ +""" + SoftLayer.tests.managers.email_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" + +from SoftLayer.managers.email import EmailManager +from SoftLayer import testing + + +class AccountManagerTests(testing.TestCase): + + def test_get_AccountOverview(self): + self.manager = EmailManager(self.client) + self.manager.get_AccountOverview(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getAccountOverview') + + def test_get_statistics(self): + self.manager = EmailManager(self.client) + self.manager.get_statistics(1232123, + ["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, True, 6) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getStatistics') From dc660d8a548562f6dd9026cec550a465696efbfa Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:09:45 -0400 Subject: [PATCH 0650/1582] fix the tp --- SoftLayer/managers/account.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d008b92a3..a613be0cc 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -293,3 +293,13 @@ def get_routers(self, mask=None, location=None): } return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) + + def get_Network_Message_Delivery_Accounts(self): + """Gets all Network Message delivery accounts. + + :returns: Network Message delivery accounts + """ + + _mask = """vendor,type""" + + return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) From 6cce16c6c9ad594449a3db5af702824806ec5f17 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 11:22:45 -0400 Subject: [PATCH 0651/1582] fix tox tool --- SoftLayer/CLI/email/detail.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 44a4bbbdb..bdc4161a6 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -13,7 +13,7 @@ @click.command() @environment.pass_env def cli(env): - """""" + """Display the Email Delivery account informatino """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) result = manager.get_Network_Message_Delivery_Accounts() @@ -25,7 +25,6 @@ def cli(env): table_information.align['username'] = 'l' for email in result: - # print(email['id']) table_information.add_row([email.get('id'), email.get('username'), email.get('emailAddress'), utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) From 00131b22f81f8daca3c3b0ebb23c8716a03ac963 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 5 May 2021 19:00:21 -0400 Subject: [PATCH 0652/1582] update the conflicts merge --- SoftLayer/CLI/hardware/detail.py | 25 +++++++++++-- .../fixtures/SoftLayer_Hardware_Server.py | 18 ++++++++++ SoftLayer/managers/hardware.py | 35 +++++++++++++++++++ tests/CLI/modules/server_tests.py | 4 +++ tests/managers/hardware_tests.py | 20 +++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/detail.py b/SoftLayer/CLI/hardware/detail.py index da62e5de6..feee666cf 100644 --- a/SoftLayer/CLI/hardware/detail.py +++ b/SoftLayer/CLI/hardware/detail.py @@ -9,13 +9,16 @@ from SoftLayer.CLI import helpers from SoftLayer import utils +# pylint: disable=R0915 + @click.command() @click.argument('identifier') @click.option('--passwords', is_flag=True, help='Show passwords (check over your shoulder!)') @click.option('--price', is_flag=True, help='Show associated prices') +@click.option('--components', is_flag=True, default=False, help='Show associated hardware components') @environment.pass_env -def cli(env, identifier, passwords, price): +def cli(env, identifier, passwords, price, components): """Get details for a hardware device.""" hardware = SoftLayer.HardwareManager(env.client) @@ -66,7 +69,7 @@ def cli(env, identifier, passwords, price): utils.clean_time(utils.lookup(result, 'lastTransaction', 'modifyDate'))) table.add_row(['last_transaction', last_transaction]) - table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else'Monthly']) + table.add_row(['billing', 'Hourly' if result['hourlyBillingFlag'] else 'Monthly']) vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: @@ -107,6 +110,24 @@ def cli(env, identifier, passwords, price): pass_table.add_row([item['username'], item['password']]) table.add_row(['remote users', pass_table]) + if components: + components = hardware.get_components(identifier) + components_table = formatting.Table(['name', 'Firmware version', 'Firmware build date', 'Type']) + components_table.align['date'] = 'l' + component_ids = [] + for hw_component in components: + if hw_component['id'] not in component_ids: + firmware = hw_component['hardwareComponentModel']['firmwares'][0] + components_table.add_row([utils.lookup(hw_component, 'hardwareComponentModel', 'longDescription'), + utils.lookup(firmware, 'version'), + utils.clean_time(utils.lookup(firmware, 'createDate')), + utils.lookup(hw_component, 'hardwareComponentModel', + 'hardwareGenericComponentModel', 'hardwareComponentType', + 'keyName')]) + component_ids.append(hw_component['id']) + + table.add_row(['components', components_table]) + table.add_row(['tags', formatting.tags(result['tagReferences'])]) env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py index 0eacab158..938c5cebc 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware_Server.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware_Server.py @@ -377,3 +377,21 @@ } } ] + +getComponents = [{ + "hardwareComponentModelId": 147, + "hardwareId": 1234, + "id": 369, + "modifyDate": "2017-11-10T16:59:38-06:00", + "serviceProviderId": 1, + "hardwareComponentModel": { + "name": "IMM2 - Onboard", + "firmwares": [ + { + "createDate": "2020-09-24T13:46:29-06:00", + "version": "5.60" + }, + { + "createDate": "2019-10-14T16:51:12-06:00", + "version": "5.10" + }]}}] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..2fec42a82 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1030,6 +1030,41 @@ def _get_disk_price_detail(self, disk_data, upgrade_prices, disk_channel, disk_t return disk_price + def get_components(self, hardware_id, mask=None, filter_component=None): + """Get details about a hardware components. + + :param int hardware_id: the instance ID + :returns: A dictionary containing a large amount of information about + the specified components. + """ + if not mask: + mask = 'id,hardwareComponentModel[longDescription,' \ + 'hardwareGenericComponentModel[description,hardwareComponentType[keyName]],' \ + 'firmwares[createDate,version]]' + + if not filter_component: + filter_component = {"components": { + "hardwareComponentModel": { + "firmwares": { + "createDate": { + "operation": "orderBy", + "options": [ + { + "name": "sort", + "value": [ + "DESC" + ] + }, + { + "name": "sortOrder", + "value": [ + 1 + ]}]} + }}}} + + return self.client.call('Hardware_Server', 'getComponents', + mask=mask, filter=filter_component, id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..4b286b351 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -985,3 +985,7 @@ def test_upgrade(self, confirm_mock): '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) + + def test_components(self): + result = self.run_command(['hardware', 'detail', '100', '--components']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..7fcac5202 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -928,6 +928,26 @@ def test_upgrade_full(self): self.assertIn({'id': 22482}, order_container['prices']) self.assertIn({'id': 50357}, order_container['prices']) + def test_get_components(self): + result = self.hardware.get_components(1234) + components = [{'hardwareComponentModelId': 147, + 'hardwareId': 1234, + 'id': 369, + 'modifyDate': '2017-11-10T16:59:38-06:00', + 'serviceProviderId': 1, + 'hardwareComponentModel': + {'name': 'IMM2 - Onboard', + 'firmwares': + [{'createDate': '2020-09-24T13:46:29-06:00', + 'version': '5.60'}, + {'createDate': '2019-10-14T16:51:12-06:00', + 'version': '5.10'}]}}] + self.assert_called_with('SoftLayer_Hardware_Server', 'getComponents') + self.assertEqual(result, components) + self.assertEqual(result[0]['hardwareId'], 1234) + self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') + self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + class HardwareHelperTests(testing.TestCase): From 180f7a1f795644815871b2bb2b4e4499b578b742 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 6 May 2021 18:38:14 -0400 Subject: [PATCH 0653/1582] fix team code review comments --- SoftLayer/CLI/email/{detail.py => list.py} | 16 +++++++--------- SoftLayer/CLI/routes.py | 2 +- SoftLayer/managers/account.py | 2 +- SoftLayer/managers/email.py | 22 +++++++++++----------- tests/CLI/modules/email_tests.py | 2 +- tests/managers/email_tests.py | 9 +++------ 6 files changed, 24 insertions(+), 29 deletions(-) rename SoftLayer/CLI/email/{detail.py => list.py} (82%) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/list.py similarity index 82% rename from SoftLayer/CLI/email/detail.py rename to SoftLayer/CLI/email/list.py index bdc4161a6..05c096250 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get details for an image.""" +"""Get lists Email Delivery account Service """ # :license: MIT, see LICENSE for more details. import click @@ -13,10 +13,10 @@ @click.command() @environment.pass_env def cli(env): - """Display the Email Delivery account informatino """ + """Lists Email Delivery Service """ manager = AccountManager(env.client) email_manager = EmailManager(env.client) - result = manager.get_Network_Message_Delivery_Accounts() + result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) @@ -29,10 +29,8 @@ def cli(env): utils.lookup(email, 'type', 'description'), utils.lookup(email, 'vendor', 'keyName')]) - overview_table = _build_overview_table(email_manager.get_AccountOverview(email.get('id'))) - statistics = email_manager.get_statistics(email.get('id'), - ["requests", "delivered", "opens", "clicks", "bounds"], - True, True, True, 6) + overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) + statistics = email_manager.get_statistics(email.get('id')) table.add_row(['email information', table_information]) table.add_row(['email overview', overview_table]) @@ -43,7 +41,7 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' @@ -57,7 +55,7 @@ def _build_overview_table(email_overview): def _build_statistics_table(statistics): - table = formatting.KeyValueTable(['name', 'value']) + table = formatting.Table(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 0c6f322b7..6037fa265 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -120,7 +120,7 @@ ('block:volume-set-note', 'SoftLayer.CLI.block.set_note:cli'), ('email', 'SoftLayer.CLI.email'), - ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:list', 'SoftLayer.CLI.email.list:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index a613be0cc..b7398076d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -294,7 +294,7 @@ def get_routers(self, mask=None, location=None): return self.client['SoftLayer_Account'].getRouters(filter=object_filter, mask=mask) - def get_Network_Message_Delivery_Accounts(self): + def get_network_message_delivery_accounts(self): """Gets all Network Message delivery accounts. :returns: Network Message delivery accounts diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index 5b5f49e00..ce80a9298 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -1,7 +1,7 @@ """ - SoftLayer.account + SoftLayer.email ~~~~~~~~~~~~~~~~~~~~~~~ - Account manager + Email manager :license: MIT, see License for more details. """ @@ -14,7 +14,7 @@ class EmailManager(utils.IdentifierMixin, object): - """Common functions for getting information from the Account service + """Common functions for getting information from the email service :param SoftLayer.API.BaseClient client: the client instance """ @@ -22,7 +22,7 @@ class EmailManager(utils.IdentifierMixin, object): def __init__(self, client): self.client = client - def get_AccountOverview(self, identifier): + def get_account_overview(self, identifier): """Gets all the Network Message Delivery Account Overview :returns: Network Message Delivery Account overview @@ -30,16 +30,16 @@ def get_AccountOverview(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview', id=identifier) - def get_statistics(self, identifier, selectedStatistics, - startDate, endDate, aggregatesOnly, days): - """Gets statistics Network Message Delivery Account + def get_statistics(self, identifier, days=30): + """gets statistics from email accounts + :days: range number :returns: statistics Network Message Delivery Account """ - body = [selectedStatistics, - startDate, - endDate, - aggregatesOnly, + body = [["requests", "delivered", "opens", "clicks", "bounds"], + True, + True, + True, days ] diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index db085efbf..798745a7f 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -10,6 +10,6 @@ class EmailCLITests(testing.TestCase): def test_detail(self): - result = self.run_command(['email', 'detail']) + result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b573326fa..b8a2b58b6 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -8,19 +8,16 @@ from SoftLayer import testing -class AccountManagerTests(testing.TestCase): +class EmailManagerTests(testing.TestCase): def test_get_AccountOverview(self): self.manager = EmailManager(self.client) - self.manager.get_AccountOverview(1232123) + self.manager.get_account_overview(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getAccountOverview') def test_get_statistics(self): self.manager = EmailManager(self.client) - self.manager.get_statistics(1232123, - ["requests", "delivered", "opens", "clicks", "bounds"], - True, - True, True, 6) + self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') From f8d5882143e425c3c061442c963d524780ec9ecd Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 7 May 2021 08:54:58 -0400 Subject: [PATCH 0654/1582] add documentation --- docs/cli/email.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 67d5319db..574fba1bc 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -4,6 +4,6 @@ Email Commands ================= -.. click:: SoftLayer.CLI.email.detail:cli - :prog: email detail +.. click:: SoftLayer.CLI.email.list:cli + :prog: email list :show-nested: \ No newline at end of file From 66a3cde4255d742cbd858d758c11352b26075f09 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:15:05 -0400 Subject: [PATCH 0655/1582] #1481 refactor block and file cli to shows the whole notes in a format json output --- SoftLayer/CLI/block/list.py | 28 +++---------------------- SoftLayer/CLI/environment.py | 4 ++++ SoftLayer/CLI/file/list.py | 23 ++------------------ SoftLayer/CLI/storage_utils.py | 38 +++++++++++++++++++++++++++++++++- 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/SoftLayer/CLI/block/list.py b/SoftLayer/CLI/block/list.py index 769aad233..bd14c7282 100644 --- a/SoftLayer/CLI/block/list.py +++ b/SoftLayer/CLI/block/list.py @@ -5,8 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting - +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -18,7 +17,7 @@ 'storage_type', lambda b: b['storageType']['keyName'].split('_').pop(0) if 'storageType' in b and 'keyName' in b['storageType'] - and isinstance(b['storageType']['keyName'], str) + and isinstance(b['storageType']['keyName'], str) else '-', mask="storageType.keyName"), column_helper.Column('capacity_gb', ('capacityGb',), mask="capacityGb"), @@ -52,8 +51,6 @@ 'notes' ] -DEFAULT_NOTES_SIZE = 20 - @click.command() @click.option('--username', '-u', help='Volume username') @@ -78,24 +75,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(block_volumes) - - for block_volume in block_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(block_volume)]) - + table = storage_utils.build_output_table(env, block_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(block_volumes): - """Reduces the size of the notes in a volume list. - - :param block_volumes: An list of block volumes - """ - for block_volume in block_volumes: - if len(block_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = block_volume['notes'][:DEFAULT_NOTES_SIZE] - block_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index e16c5cde9..c96f4a7c3 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -52,6 +52,10 @@ def fmt(self, output, fmt=None): fmt = self.format return formatting.format_output(output, fmt) + def format_output_is_json(self): + """Return True if format output is json or jsonraw""" + return 'json' in self.format + def fout(self, output, newline=True): """Format the input and output to the console (stdout).""" if output is not None: diff --git a/SoftLayer/CLI/file/list.py b/SoftLayer/CLI/file/list.py index f7c08fe18..ba72c4fe0 100644 --- a/SoftLayer/CLI/file/list.py +++ b/SoftLayer/CLI/file/list.py @@ -5,7 +5,7 @@ import SoftLayer from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment -from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils COLUMNS = [ column_helper.Column('id', ('id',), mask="id"), @@ -76,24 +76,5 @@ def cli(env, sortby, columns, datacenter, username, storage_type, order): order=order, mask=columns.mask()) - table = formatting.Table(columns.columns) - table.sortby = sortby - - _reduce_notes(file_volumes) - - for file_volume in file_volumes: - table.add_row([value or formatting.blank() - for value in columns.row(file_volume)]) - + table = storage_utils.build_output_table(env, file_volumes, columns, sortby) env.fout(table) - - -def _reduce_notes(file_volumes): - """Reduces the size of the notes in a volume list. - - :param file_volumes: An list of file volumes - """ - for file_volume in file_volumes: - if len(file_volume.get('notes', '')) > DEFAULT_NOTES_SIZE: - shortened_notes = file_volume['notes'][:DEFAULT_NOTES_SIZE] - file_volume['notes'] = shortened_notes diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index 47c888960..aa24585eb 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -2,6 +2,43 @@ # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import columns as column_helper +from SoftLayer.CLI import formatting + +DEFAULT_NOTES_SIZE = 20 + + +def reduce_notes(volumes, env): + """Reduces all long notes found in the volumes list just if the format output is different from a JSON format. + + :param list volumes: An list of storage volumes + :param env :A environment console. + """ + if env.format_output_is_json(): + return + + for volume in volumes: + if len(volume.get('notes', '')) > DEFAULT_NOTES_SIZE: + shortened_notes = volume['notes'][:DEFAULT_NOTES_SIZE] + volume['notes'] = shortened_notes + + +def build_output_table(env, volumes, columns, sortby): + """Builds a formatting table for a list of volumes. + + :param env :A Environment console. + :param list volumes: An list of storage volumes + :param columns :A ColumnFormatter for column names + :param str sortby :A string to sort by. + """ + table = formatting.Table(columns.columns) + if sortby in table.columns: + table.sortby = sortby + + reduce_notes(volumes, env) + for volume in volumes: + table.add_row([value or formatting.blank() + for value in columns.row(volume)]) + return table def _format_name(obj): @@ -96,7 +133,6 @@ def _format_name(obj): """), ] - DEFAULT_COLUMNS = [ 'id', 'name', From c1de463a2b98720107c07484a31687a5b3acdb9b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 7 May 2021 12:16:12 -0400 Subject: [PATCH 0656/1582] #1481 add tests to block and file cli to shows the whole notes in a format json output --- tests/CLI/environment_tests.py | 4 ++++ tests/CLI/modules/block_tests.py | 35 ++++++++++++++++++++++++++++++-- tests/CLI/modules/file_tests.py | 32 +++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/CLI/environment_tests.py b/tests/CLI/environment_tests.py index f194bdc87..8829bf93d 100644 --- a/tests/CLI/environment_tests.py +++ b/tests/CLI/environment_tests.py @@ -81,3 +81,7 @@ def test_print_unicode(self, echo): ] self.env.fout(output) self.assertEqual(2, echo.call_count) + + def test_format_output_is_json(self): + self.env.format = 'jsonraw' + self.assertTrue(self.env.format_output_is_json()) diff --git a/tests/CLI/modules/block_tests.py b/tests/CLI/modules/block_tests.py index c38a7544d..1c6b22e14 100644 --- a/tests/CLI/modules/block_tests.py +++ b/tests/CLI/modules/block_tests.py @@ -5,10 +5,10 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerAPIError from SoftLayer import testing - import json from unittest import mock as mock @@ -135,7 +135,7 @@ def test_volume_list(self): 'IOPs': None, 'ip_addr': '10.1.2.3', 'lunId': None, - 'notes': "{'status': 'availabl", + 'notes': "{'status': 'available'}", 'rep_partner_count': None, 'storage_type': 'ENDURANCE', 'username': 'username', @@ -143,6 +143,37 @@ def test_volume_list(self): }], json.loads(result.output)) + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.BlockStorageManager.list_block_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table)+'\n' + result = self.run_command(['--format', 'table', 'block', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + def test_volume_list_order(self): result = self.run_command(['block', 'volume-list', '--order=1234567']) diff --git a/tests/CLI/modules/file_tests.py b/tests/CLI/modules/file_tests.py index 442ca067d..cbe73818c 100644 --- a/tests/CLI/modules/file_tests.py +++ b/tests/CLI/modules/file_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting from SoftLayer import SoftLayerError from SoftLayer import testing @@ -63,6 +64,37 @@ def test_volume_list_order(self): json_result = json.loads(result.output) self.assertEqual(json_result[0]['id'], 1) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_notes_format_output_json(self, list_mock): + note_mock = 'test ' * 5 + list_mock.return_value = [ + {'notes': note_mock} + ] + + result = self.run_command(['--format', 'json', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual( + [{ + 'notes': note_mock, + }], + json.loads(result.output)) + + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') + def test_volume_list_reduced_notes_format_output_table(self, list_mock): + note_mock = 'test ' * 10 + expected_reduced_note = 'test ' * 4 + list_mock.return_value = [ + {'notes': note_mock} + ] + expected_table = formatting.Table(['notes']) + expected_table.add_row([expected_reduced_note]) + expected_output = formatting.format_output(expected_table) + '\n' + result = self.run_command(['--format', 'table', 'file', 'volume-list', '--columns', 'notes']) + + self.assert_no_fail(result) + self.assertEqual(expected_output, result.output) + @mock.patch('SoftLayer.FileStorageManager.list_file_volumes') def test_volume_count(self, list_mock): list_mock.return_value = [ From 1572d8ceb9865dfec714a9a83cad7043ab544f0d Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 10 May 2021 14:57:08 -0400 Subject: [PATCH 0657/1582] fix team code review comments --- SoftLayer/CLI/email/list.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 05c096250..a5353a31a 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -19,7 +19,8 @@ def cli(env): result = manager.get_network_message_delivery_accounts() table = formatting.KeyValueTable(['name', 'value']) - + table.align['name'] = 'r' + table.align['value'] = 'l' table_information = formatting.KeyValueTable(['id', 'username', 'hostname', 'description', 'vendor']) table_information.align['id'] = 'r' table_information.align['username'] = 'l' @@ -32,8 +33,8 @@ def cli(env): overview_table = _build_overview_table(email_manager.get_account_overview(email.get('id'))) statistics = email_manager.get_statistics(email.get('id')) - table.add_row(['email information', table_information]) - table.add_row(['email overview', overview_table]) + table.add_row(['email_information', table_information]) + table.add_row(['email_overview', overview_table]) for statistic in statistics: table.add_row(['statistics', _build_statistics_table(statistic)]) @@ -41,29 +42,24 @@ def cli(env): def _build_overview_table(email_overview): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['creditsAllowed', email_overview.get('creditsAllowed')]) - table.add_row(['creditsRemain', email_overview.get('creditsRemain')]) - table.add_row(['package', email_overview.get('package')]) - table.add_row(['reputation', email_overview.get('reputation')]) - table.add_row(['requests', email_overview.get('requests')]) + table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('package'), email_overview.get('reputation'), + email_overview.get('requests')]) return table def _build_statistics_table(statistics): - table = formatting.Table(['name', 'value']) + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['delivered', statistics.get('delivered')]) - table.add_row(['requests', statistics.get('requests')]) - table.add_row(['bounces', statistics.get('bounces')]) - table.add_row(['opens', statistics.get('opens')]) - table.add_row(['clicks', statistics.get('clicks')]) - table.add_row(['spam Reports', statistics.get('spamReports')]) + table.add_row([statistics.get('delivered'), statistics.get('requests'), + statistics.get('bounces'), statistics.get('opens'), + statistics.get('clicks'), statistics.get('spamReports')]) return table From 7b826af01d80a0150a0732e20ce939a4e5eef233 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 13 May 2021 09:17:49 -0400 Subject: [PATCH 0658/1582] add new email features, email detail and email edit --- SoftLayer/CLI/email/__init__.py | 1 + SoftLayer/CLI/email/detail.py | 33 +++++++++++++++++++ SoftLayer/CLI/email/edit.py | 32 ++++++++++++++++++ SoftLayer/CLI/routes.py | 2 ++ ...Network_Message_Delivery_Email_Sendgrid.py | 31 +++++++++++++++++ SoftLayer/managers/email.py | 30 +++++++++++++++++ docs/cli.rst | 1 + docs/cli/email.rst | 8 +++++ tests/CLI/modules/email_tests.py | 16 ++++++++- tests/managers/email_tests.py | 6 ++++ 10 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/email/detail.py create mode 100644 SoftLayer/CLI/email/edit.py diff --git a/SoftLayer/CLI/email/__init__.py b/SoftLayer/CLI/email/__init__.py index e69de29bb..b765cbe50 100644 --- a/SoftLayer/CLI/email/__init__.py +++ b/SoftLayer/CLI/email/__init__.py @@ -0,0 +1 @@ +"""Network Delivery account""" diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py new file mode 100644 index 000000000..28fccba57 --- /dev/null +++ b/SoftLayer/CLI/email/detail.py @@ -0,0 +1,33 @@ +"""Display details for a specified email.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.email import EmailManager +from SoftLayer import utils + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Display details for a specified email.""" + + email_manager = EmailManager(env.client) + result = email_manager.get_instance(identifier) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + table.add_row(['id', result.get('id')]) + table.add_row(['username', result.get('username')]) + table.add_row(['create_date', result.get('createDate')]) + table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) + table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) + table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) + table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py new file mode 100644 index 000000000..321e6306f --- /dev/null +++ b/SoftLayer/CLI/email/edit.py @@ -0,0 +1,32 @@ +"""Edit details of an Delivery email account.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.email import EmailManager + + +@click.command() +@click.argument('identifier') +@click.option('--username', help="username account") +@click.option('--email', help="Additional note for the image") +@click.option('--password', + help="Password must be between 8 and 20 characters " + "and must contain one letter and one number.") +@environment.pass_env +def cli(env, identifier, username, email, password): + """Edit details of an email delivery account.""" + data = {} + if username: + data['username'] = username + if email: + data['emailAddress'] = email + if password: + data['password'] = password + + email_manager = EmailManager(env.client) + + if not email_manager.editObject(identifier, data): + raise exceptions.CLIAbort("Failed to Edit email account") diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 6037fa265..38a716434 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -121,6 +121,8 @@ ('email', 'SoftLayer.CLI.email'), ('email:list', 'SoftLayer.CLI.email.list:cli'), + ('email:detail', 'SoftLayer.CLI.email.detail:cli'), + ('email:edit', 'SoftLayer.CLI.email.edit:cli'), ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 94fd169fd..9bff8ad08 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -25,3 +25,34 @@ "uniqueOpens": 0, "unsubscribes": 0 }] + +getObject = { + "accountId": 123456, + "createDate": "2020-07-06T10:29:11-06:00", + "id": 1232123, + "password": "Test123456789", + "typeId": 21, + "username": "techsupport3@ie.ibm.com", + "vendorId": 1, + "billingItem": { + "categoryCode": "network_message_delivery", + "description": "Free Package", + "id": 695735054, + "notes": "techsupport3@ie.ibm.com", + }, + "type": { + "description": "Delivery of messages through e-mail", + "id": 21, + "keyName": "EMAIL", + "name": "Email" + }, + "vendor": { + "id": 1, + "keyName": "SENDGRID", + "name": "SendGrid" + }, + "emailAddress": "techsupport3@ie.ibm.com", + "smtpAccess": "1" +} + +editObject = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index ce80a9298..df3c144e5 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -45,3 +45,33 @@ def get_statistics(self, identifier, days=30): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics', id=identifier, *body) + + def get_instance(self, identifier): + """Gets the Network_Message_Delivery_Email_Sendgrid instance + + :return: Network_Message_Delivery_Email_Sendgrid + """ + + _mask = """emailAddress,type,billingItem,vendor""" + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject', id=identifier, mask=_mask) + + def editObject(self, identifier, username=None, emailAddress=None, password=None): + """Edit email delivery account related details. + + :param int identifier: The ID of the email account + :param string username: username of the email account. + :param string email: email of the email account. + :param string password: password of the email account to be updated to. + """ + data = {} + if username: + data['username'] = username + if emailAddress: + data['emailAddress'] = emailAddress + if password: + data['password'] = password + + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject', data, id=identifier) diff --git a/docs/cli.rst b/docs/cli.rst index a659b145c..264f5bcaf 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -79,6 +79,7 @@ To discover the available commands, simply type `slcli`. config CLI configuration. dedicatedhost Dedicated Host. dns Domain Name System. + email Email Deliviry Network event-log Event Logs. file File Storage. firewall Firewalls. diff --git a/docs/cli/email.rst b/docs/cli/email.rst index 574fba1bc..732d524d7 100644 --- a/docs/cli/email.rst +++ b/docs/cli/email.rst @@ -6,4 +6,12 @@ Email Commands .. click:: SoftLayer.CLI.email.list:cli :prog: email list + :show-nested: + +.. click:: SoftLayer.CLI.email.detail:cli + :prog: email detail + :show-nested: + +.. click:: SoftLayer.CLI.email.edit:cli + :prog: email edit :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index 798745a7f..f2c8aa693 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -9,7 +9,21 @@ class EmailCLITests(testing.TestCase): - def test_detail(self): + def test_list(self): result = self.run_command(['email', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkMessageDeliveryAccounts') + + def test_detail(self): + result = self.run_command(['email', 'detail', '1232123']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_edit(self): + result = self.run_command(['email', 'edit', '1232123', + '--username=test@ibm.com', + '--email=test@ibm.com', + '--password=test123456789']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'editObject') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index b8a2b58b6..fad965c7a 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -21,3 +21,9 @@ def test_get_statistics(self): self.manager.get_statistics(1232123, 6) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getStatistics') + + def test_get_object(self): + self.manager = EmailManager(self.client) + self.manager.get_instance(1232123) + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'getObject') From 1671c767ba4442d810d1278e03ac1df323bbe6ca Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:24:36 -0500 Subject: [PATCH 0659/1582] #1486 fixed newly failing unit tests --- SoftLayer/CLI/virt/create.py | 2 +- tests/CLI/modules/vs/vs_create_tests.py | 24 +++++++----------------- tests/api_tests.py | 13 +++++++++++++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 23a18457d..9d07f01d2 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -177,7 +177,7 @@ def _parse_create_args(client, args): help="Forces the VS to only have access the private network") @click.option('--like', is_eager=True, callback=_update_with_like_args, help="Use the configuration from an existing VS") -@click.option('--network', '-n', help="Network port speed in Mbps") +@click.option('--network', '-n', help="Network port speed in Mbps", type=click.INT) @helpers.multi_option('--tag', '-g', help="Tags to add to the instance") @click.option('--template', '-t', is_eager=True, callback=template.TemplateCallback(list_args=['disk', 'key', 'tag']), diff --git a/tests/CLI/modules/vs/vs_create_tests.py b/tests/CLI/modules/vs/vs_create_tests.py index e9bb2fd74..efcb8cbc3 100644 --- a/tests/CLI/modules/vs/vs_create_tests.py +++ b/tests/CLI/modules/vs/vs_create_tests.py @@ -42,7 +42,7 @@ def test_create(self, confirm_mock): 'hostname': 'host', 'startCpus': 2, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}], + 'networkComponents': [{'maxSpeed': 100}], 'supplementalCreateObjectOptions': {'bootMode': None}},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -225,7 +225,7 @@ def test_create_with_integer_image_guid(self, confirm_mock): 'supplementalCreateObjectOptions': {'bootMode': None}, 'blockDeviceTemplateGroup': {'globalIdentifier': 'aaaa1111bbbb2222'}, 'datacenter': {'name': 'dal05'}, - 'networkComponents': [{'maxSpeed': '100'}] + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -257,7 +257,7 @@ def test_create_with_flavor(self, confirm_mock): 'bootMode': None, 'flavorKeyName': 'B1_1X2X25'}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'networkComponents': [{'maxSpeed': '100'}]},) + 'networkComponents': [{'maxSpeed': 100}]},) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=args) @@ -351,21 +351,11 @@ def test_create_with_host_id(self, confirm_mock): 'domain': 'example.com', 'localDiskFlag': True, 'hourlyBillingFlag': True, - 'supplementalCreateObjectOptions': { - 'bootMode': None - }, - 'dedicatedHost': { - 'id': 123 - }, + 'supplementalCreateObjectOptions': {'bootMode': None}, + 'dedicatedHost': {'id': 123}, 'operatingSystemReferenceCode': 'UBUNTU_LATEST', - 'datacenter': { - 'name': 'dal05' - }, - 'networkComponents': [ - { - 'maxSpeed': '100' - } - ] + 'datacenter': {'name': 'dal05'}, + 'networkComponents': [{'maxSpeed': 100}] },) self.assert_called_with('SoftLayer_Virtual_Guest', 'generateOrderTemplate', args=template_args) diff --git a/tests/api_tests.py b/tests/api_tests.py index a83246858..c3d84529a 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,6 +96,19 @@ def test_simple_call(self): offset=None, ) + + def test_simple_call_2(self): + mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') + mock.return_value = {"test": "result"} + + resp = self.client.call('SERVICE', 'METHOD', {'networkComponents': [{'maxSpeed': 100}]}) + + self.assertEqual(resp, {"test": "result"}) + self.assert_called_with('SoftLayer_SERVICE', 'METHOD', + mask=None, filter=None, identifier=None, + args=({'networkComponents': [{'maxSpeed': 100}]},), limit=None, offset=None, + ) + def test_verify_request_false(self): client = SoftLayer.BaseClient(transport=self.mocks) mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') From eff3bf2153368f01724d8e265496ac68ee419999 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 14 May 2021 15:30:47 -0500 Subject: [PATCH 0660/1582] fixed tox --- tests/api_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/api_tests.py b/tests/api_tests.py index c3d84529a..ea4726a6e 100644 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -96,7 +96,6 @@ def test_simple_call(self): offset=None, ) - def test_simple_call_2(self): mock = self.set_mock('SoftLayer_SERVICE', 'METHOD') mock.return_value = {"test": "result"} From 96889809e04f3aa4114c7e102fa27fef838e5364 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 17 May 2021 18:21:33 -0400 Subject: [PATCH 0661/1582] Add a table result for the hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 97 +++++++++++- SoftLayer/fixtures/SoftLayer_Product_Order.py | 148 ++++++++++++++++++ ...ftLayer_Provisioning_Maintenance_Window.py | 26 +++ SoftLayer/managers/hardware.py | 44 ++++-- tests/CLI/modules/server_tests.py | 49 +++--- tests/managers/hardware_tests.py | 10 +- 6 files changed, 335 insertions(+), 39 deletions(-) create mode 100644 SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 037506df0..89b89859e 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -2,6 +2,7 @@ # :license: MIT, see LICENSE for more details. import click +from SoftLayer import utils import SoftLayer from SoftLayer.CLI import environment @@ -35,6 +36,9 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad """Upgrade a Hardware Server.""" mgr = SoftLayer.HardwareManager(env.client) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' if not any([memory, network, drive_controller, public_bandwidth, add_disk, resize_disk]): raise exceptions.ArgumentError("Must provide " @@ -57,7 +61,92 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad disks = {'description': 'resize_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} disk_list.append(disks) - if not mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, - public_bandwidth=public_bandwidth, disk=disk_list, test=test): - raise exceptions.CLIAbort('Hardware Server Upgrade Failed') - env.fout('Successfully Upgraded.') + response = mgr.upgrade(hw_id, memory=memory, nic_speed=network, drive_controller=drive_controller, + public_bandwidth=public_bandwidth, disk=disk_list, test=test) + + if response: + if test: + add_data_to_table(response, table) + else: + table.add_row(['order_date', response.get('orderDate')]) + table.add_row(['order_id', response.get('orderId')]) + add_data_to_table(response['orderDetails'], table) + place_order_table = get_place_order_information(response) + table.add_row(['Place Order Information', place_order_table]) + order_detail_table = get_order_detail(response) + table.add_row(['Order Detail (Billing Information)', order_detail_table]) + + env.fout(table) + + +def add_data_to_table(response, table): + """Add the hardware server upgrade result to the table""" + table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) + table.add_row(['quantity', response.get('quantity')]) + table.add_row(['package_id', response.get('packageId')]) + table.add_row(['currency_short_name', response.get('currencyShortName')]) + table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) + table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) + table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table_hardware = get_hardware_detail(response) + table.add_row(['Hardware', table_hardware]) + table_prices = get_hardware_prices(response) + table.add_row(['prices', table_prices]) + + +def get_place_order_information(response): + """Get the hardware server place order information.""" + table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order.add_row([response.get('id'), + response.get('accountId'), + response.get('status'), + utils.lookup(response, 'account', 'companyName'), + utils.lookup(response, 'userRecord', 'firstName'), + utils.lookup(response, 'account', 'lastName'), + utils.lookup(response, 'account', 'username')]) + + return table_place_order + + +def get_hardware_detail(response): + """Get the hardware server detail.""" + table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) + for hardware in response['hardware']: + table_hardware.add_row([hardware.get('accountId'), + hardware.get('hostname'), + hardware.get('domain')]) + + return table_hardware + + +def get_hardware_prices(response): + """Get the hardware server prices.""" + table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + 'Item Units']) + for price in response['prices']: + categories = price.get('categories')[0] + table_prices.add_row([price.get('id'), + price.get('hourlyRecurringFee'), + price.get('recurringFee'), + categories.get('name'), + utils.lookup(price, 'item', 'description'), + utils.lookup(price, 'item', 'units')]) + + return table_prices + + +def get_order_detail(response): + """Get the hardware server order detail.""" + table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', + 'billing_name_first', 'billing_name_last', 'billing_postal_code', + 'billing_state']) + table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameFirst'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingNameLast'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingPostalCode'), + utils.lookup(response, 'orderDetails', 'billingInformation', 'billingState')]) + + return table_order_detail diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..83604f8d1 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -63,6 +63,154 @@ 'postTaxRecurring': '0.32', } +hardware_verifyOrder = { + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111, + "domain": "testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "units": "GB" + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "sendQuoteEmailFlag": None, + "totalRecurringTax": "0", + "useHourlyPricing": False +} + +hardware_placeOrder = { + "orderDate": "2021-05-07T07:41:41-06:00", + "orderDetails": { + "billingInformation": { + "billingAddressLine1": "4849 Alpha Rd", + "billingCity": "Dallas", + "billingCountryCode": "US", + "billingEmail": "test.ibm.com", + "billingNameCompany": "SoftLayer Internal - Development Community", + "billingNameFirst": "Test", + "billingNameLast": "Test", + "billingPhoneVoice": "1111111", + "billingPostalCode": "75244-1111", + "billingState": "TX", + }, + "currencyShortName": "USD", + "hardware": [ + { + "accountId": 1111111, + "bareMetalInstanceFlag": 0, + "domain": "testedit.com", + "fullyQualifiedDomainName": "bardcabero.testedit.com", + "hostname": "bardcabero", + "globalIdentifier": "81434794-af69-44d5-bb97-1111111" + } + ], + "location": "1441195", + "locationObject": { + "id": 1441195, + "longName": "Dallas 10", + "name": "dal10" + }, + "packageId": 911, + "paymentType": "ADD_TO_BALANCE", + "postTaxRecurring": "0", + "postTaxRecurringHourly": "0", + "postTaxRecurringMonthly": "0", + "postTaxSetup": "0", + "preTaxRecurring": "0", + "preTaxRecurringHourly": "0", + "preTaxRecurringMonthly": "0", + "preTaxSetup": "0", + "prices": [ + { + "hourlyRecurringFee": "0", + "id": 209391, + "recurringFee": "0", + "categories": [ + { + "categoryCode": "ram", + "id": 3, + "name": "RAM" + } + ], + "item": { + "capacity": "32", + "description": "32 GB RAM", + "id": 11291, + "keyName": "RAM_32_GB_DDR4_2133_ECC_NON_REG", + "units": "GB", + } + } + ], + "proratedInitialCharge": "0", + "proratedOrderTotal": "0", + "quantity": 1, + "totalRecurringTax": "0", + "useHourlyPricing": False + }, + "orderId": 78332111, + "placedOrder": { + "accountId": 1111111, + "id": 1234, + "status": "PENDING_UPGRADE", + "account": { + "brandId": 2, + "companyName": "SoftLayer Internal - Development Community", + "id": 1234 + }, + "items": [ + { + "categoryCode": "ram", + "description": "32 GB RAM", + "id": 824199364, + "recurringFee": "0" + } + ], + "userRecord": { + "accountId": 1234, + "firstName": "test", + "id": 3333, + "lastName": "cabero", + "username": "sl1234-dcabero" + } + } +} + rsc_placeOrder = { 'orderDate': '2013-08-01 15:23:45', 'orderId': 1234, diff --git a/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py new file mode 100644 index 000000000..f3e597481 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Provisioning_Maintenance_Window.py @@ -0,0 +1,26 @@ +getMaintenanceWindows = [ + { + "beginDate": "2021-05-13T16:00:00-06:00", + "dayOfWeek": 4, + "endDate": "2021-05-13T19:00:00-06:00", + "id": 198351, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDat": "2021-05-14T00:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T03:00:00-06:00", + "id": 189747, + "locationId": 1441195, + "portalTzId": 114 + }, + { + "beginDate": "2021-05-14T08:00:00-06:00", + "dayOfWeek": 5, + "endDate": "2021-05-14T11:00:00-06:00", + "id": 198355, + "locationId": 1441195, + "portalTzId": 114 + } +] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8e63bc9e9..4643adcb8 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -834,6 +834,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ + result = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -849,14 +850,24 @@ def upgrade(self, instance_id, memory=None, server_response = self.get_instance(instance_id) package_id = server_response['billingItem']['package']['id'] + location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) + maintenance_window_id = self.get_maintenance_windows_id(location_id) + order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', - 'properties': [{ - 'name': 'MAINTENANCE_WINDOW', - 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") - }], + 'properties': [ + { + 'name': 'MAINTENANCE_WINDOW', + 'value': maintenance_window.strftime("%Y-%m-%d %H:%M:%S%z") + }, + { + 'name': 'MAINTENANCE_WINDOW_ID', + 'value': str(maintenance_window_id) + } + + ], 'hardware': [{'id': int(instance_id)}], 'packageId': package_id } @@ -878,11 +889,26 @@ def upgrade(self, instance_id, memory=None, if prices: if test: - self.client['Product_Order'].verifyOrder(order) + result = self.client['Product_Order'].verifyOrder(order) else: - self.client['Product_Order'].placeOrder(order) - return True - return False + result = self.client['Product_Order'].placeOrder(order) + return result + + def get_maintenance_windows_id(self, location_id): + """Get the disks prices to be added or upgraded. + + :param int location_id: Hardware Server location id. + :return int. + """ + begin_date_object = datetime.datetime.now() + begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + end_date_object = datetime.date.today() + datetime.timedelta(days=30) + end_date = end_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") + + result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, + end_date, + location_id) + return result_windows[0]['id'] @retry(logger=LOGGER) def get_instance(self, instance_id): @@ -893,7 +919,7 @@ def get_instance(self, instance_id): the specified instance. """ mask = [ - 'billingItem[id,package[id,keyName],nextInvoiceChildren]' + 'datacenter,billingItem[id,package[id,keyName],nextInvoiceChildren]' ] mask = "mask[%s]" % ','.join(mask) diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 2283e3035..3a400842f 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -14,6 +14,7 @@ from unittest import mock as mock from SoftLayer.CLI import exceptions +from SoftLayer.fixtures import SoftLayer_Product_Order from SoftLayer import SoftLayerError from SoftLayer import testing @@ -691,19 +692,19 @@ def test_dns_sync_both(self, confirm_mock): 'getResourceRecords') getResourceRecords.return_value = [] createAargs = ({ - 'type': 'a', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '172.16.1.100', - 'ttl': 7200 - },) + 'type': 'a', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '172.16.1.100', + 'ttl': 7200 + },) createPTRargs = ({ - 'type': 'ptr', - 'host': '100', - 'domainId': 123456, - 'data': 'hardware-test1.test.sftlyr.ws', - 'ttl': 7200 - },) + 'type': 'ptr', + 'host': '100', + 'domainId': 123456, + 'data': 'hardware-test1.test.sftlyr.ws', + 'ttl': 7200 + },) result = self.run_command(['hw', 'dns-sync', '1000']) @@ -746,12 +747,12 @@ def test_dns_sync_v6(self, confirm_mock): } } createV6args = ({ - 'type': 'aaaa', - 'host': 'hardware-test1', - 'domainId': 12345, # from SoftLayer_Account::getDomains - 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', - 'ttl': 7200 - },) + 'type': 'aaaa', + 'host': 'hardware-test1', + 'domainId': 12345, # from SoftLayer_Account::getDomains + 'data': '2607:f0d0:1b01:0023:0000:0000:0000:0004', + 'ttl': 7200 + },) server.return_value = test_server result = self.run_command(['hw', 'dns-sync', '--aaaa-record', '1000']) self.assert_no_fail(result) @@ -936,9 +937,9 @@ def test_upgrade_aborted(self, confirm_mock): self.assertEqual(result.exit_code, 2) self.assertIsInstance(result.exception, exceptions.CLIAbort) - @mock.patch('SoftLayer.CLI.formatting.confirm') - def test_upgrade_test(self, confirm_mock): - confirm_mock.return_value = True + def test_upgrade_test(self): + order_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_verifyOrder result = self.run_command(['hw', 'upgrade', '100', '--test', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) self.assert_no_fail(result) @@ -946,6 +947,8 @@ def test_upgrade_test(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_add_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--add-disk=1000', '2']) self.assert_no_fail(result) @@ -953,6 +956,8 @@ def test_upgrade_add_disk(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade_resize_disk(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--resize-disk=1000', '1']) self.assert_no_fail(result) @@ -981,6 +986,8 @@ def test_upgrade_disk_does_not_exist(self, confirm_mock): @mock.patch('SoftLayer.CLI.formatting.confirm') def test_upgrade(self, confirm_mock): confirm_mock.return_value = True + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.hardware_placeOrder result = self.run_command(['hw', 'upgrade', '100', '--memory=32', '--public-bandwidth=500', '--drive-controller=RAID', '--network=10000 Redundant']) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index bf76de3c2..c3dd67a4d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -879,7 +879,7 @@ def test_get_price_id_disk_capacity(self): def test_upgrade(self): result = self.hardware.upgrade(1, memory=32) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -891,7 +891,7 @@ def test_upgrade_add_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -903,7 +903,7 @@ def test_upgrade_resize_disk(self): disk_list.append(disks) result = self.hardware.upgrade(1, disk=disk_list) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] @@ -912,14 +912,14 @@ def test_upgrade_resize_disk(self): def test_upgrade_blank(self): result = self.hardware.upgrade(1) - self.assertEqual(result, False) + self.assertEqual(result, None) self.assertEqual(self.calls('SoftLayer_Product_Order', 'placeOrder'), []) def test_upgrade_full(self): result = self.hardware.upgrade(1, memory=32, nic_speed="10000 Redundant", drive_controller="RAID", public_bandwidth=500, test=False) - self.assertEqual(result, True) + self.assertEqual(result['orderId'], 1234) self.assert_called_with('SoftLayer_Product_Order', 'placeOrder') call = self.calls('SoftLayer_Product_Order', 'placeOrder')[0] order_container = call.args[0] From 6bd61d8d982a09b44359a71633db88b4f6431844 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 18 May 2021 09:19:38 -0400 Subject: [PATCH 0662/1582] fix team code review comments --- SoftLayer/CLI/email/detail.py | 11 ++++++-- SoftLayer/CLI/email/edit.py | 25 +++++++++++++------ SoftLayer/CLI/email/list.py | 12 ++++++--- ...Network_Message_Delivery_Email_Sendgrid.py | 1 + SoftLayer/managers/email.py | 14 ++++++++--- tests/CLI/modules/email_tests.py | 2 ++ tests/managers/email_tests.py | 6 +++++ 7 files changed, 54 insertions(+), 17 deletions(-) diff --git a/SoftLayer/CLI/email/detail.py b/SoftLayer/CLI/email/detail.py index 28fccba57..8d7ca18a5 100644 --- a/SoftLayer/CLI/email/detail.py +++ b/SoftLayer/CLI/email/detail.py @@ -1,7 +1,8 @@ -"""Display details for a specified email.""" +"""Display details for a specified email account.""" # :license: MIT, see LICENSE for more details. import click +from SoftLayer.CLI.email.list import build_statistics_table from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.managers.email import EmailManager @@ -23,11 +24,17 @@ def cli(env, identifier): table.add_row(['id', result.get('id')]) table.add_row(['username', result.get('username')]) + table.add_row(['email_address', result.get('emailAddress')]) table.add_row(['create_date', result.get('createDate')]) - table.add_row(['categoryCode', utils.lookup(result, 'billingItem', 'categoryCode')]) + table.add_row(['category_code', utils.lookup(result, 'billingItem', 'categoryCode')]) table.add_row(['description', utils.lookup(result, 'billingItem', 'description')]) table.add_row(['type_description', utils.lookup(result, 'type', 'description')]) table.add_row(['type', utils.lookup(result, 'type', 'keyName')]) table.add_row(['vendor', utils.lookup(result, 'vendor', 'keyName')]) + statistics = email_manager.get_statistics(identifier) + + for statistic in statistics: + table.add_row(['statistics', build_statistics_table(statistic)]) + env.fout(table) diff --git a/SoftLayer/CLI/email/edit.py b/SoftLayer/CLI/email/edit.py index 321e6306f..16703b2aa 100644 --- a/SoftLayer/CLI/email/edit.py +++ b/SoftLayer/CLI/email/edit.py @@ -10,23 +10,32 @@ @click.command() @click.argument('identifier') -@click.option('--username', help="username account") -@click.option('--email', help="Additional note for the image") +@click.option('--username', help="Sets username for this account") +@click.option('--email', help="Sets the contact email for this account") @click.option('--password', help="Password must be between 8 and 20 characters " "and must contain one letter and one number.") @environment.pass_env def cli(env, identifier, username, email, password): """Edit details of an email delivery account.""" + email_manager = EmailManager(env.client) + data = {} + update = False + if email: + if email_manager.update_email(identifier, email): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit emailAddress account") if username: data['username'] = username - if email: - data['emailAddress'] = email if password: data['password'] = password + if len(data) != 0: + if email_manager.editObject(identifier, **data): + update = True + else: + raise exceptions.CLIAbort("Failed to Edit email account") - email_manager = EmailManager(env.client) - - if not email_manager.editObject(identifier, data): - raise exceptions.CLIAbort("Failed to Edit email account") + if update: + env.fout('Updated Successfully') diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index a5353a31a..87912b37f 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -36,25 +36,29 @@ def cli(env): table.add_row(['email_information', table_information]) table.add_row(['email_overview', overview_table]) for statistic in statistics: - table.add_row(['statistics', _build_statistics_table(statistic)]) + table.add_row(['statistics', build_statistics_table(statistic)]) env.fout(table) def _build_overview_table(email_overview): - table = formatting.Table(['credit_Allowed', 'credits_Remain', 'package', 'reputation', 'requests']) + table = formatting.Table( + ['credit_allowed', 'credits_remain', 'credits_overage', 'credits_used', + 'package', 'reputation', 'requests']) table.align['name'] = 'r' table.align['value'] = 'l' table.add_row([email_overview.get('creditsAllowed'), email_overview.get('creditsRemain'), + email_overview.get('creditsOverage'), email_overview.get('creditsUsed'), email_overview.get('package'), email_overview.get('reputation'), email_overview.get('requests')]) return table -def _build_statistics_table(statistics): - table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spamReports']) +def build_statistics_table(statistics): + """statistics records of Email Delivery account""" + table = formatting.Table(['delivered', 'requests', 'bounces', 'opens', 'clicks', 'spam_reports']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py index 9bff8ad08..6a64d1be6 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Message_Delivery_Email_Sendgrid.py @@ -56,3 +56,4 @@ } editObject = True +updateEmailAddress = True diff --git a/SoftLayer/managers/email.py b/SoftLayer/managers/email.py index df3c144e5..57af17ce7 100644 --- a/SoftLayer/managers/email.py +++ b/SoftLayer/managers/email.py @@ -57,7 +57,7 @@ def get_instance(self, identifier): return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject', id=identifier, mask=_mask) - def editObject(self, identifier, username=None, emailAddress=None, password=None): + def editObject(self, identifier, username=None, password=None): """Edit email delivery account related details. :param int identifier: The ID of the email account @@ -68,10 +68,18 @@ def editObject(self, identifier, username=None, emailAddress=None, password=None data = {} if username: data['username'] = username - if emailAddress: - data['emailAddress'] = emailAddress if password: data['password'] = password return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject', data, id=identifier) + + def update_email(self, identifier, email): + """Edit email address delivery account . + + :param int identifier: The ID of the email account + :param string email: email of the email account. + + """ + return self.client.call('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress', email, id=identifier) diff --git a/tests/CLI/modules/email_tests.py b/tests/CLI/modules/email_tests.py index f2c8aa693..0e2b398d6 100644 --- a/tests/CLI/modules/email_tests.py +++ b/tests/CLI/modules/email_tests.py @@ -27,3 +27,5 @@ def test_edit(self): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'editObject') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') diff --git a/tests/managers/email_tests.py b/tests/managers/email_tests.py index fad965c7a..94ea84b52 100644 --- a/tests/managers/email_tests.py +++ b/tests/managers/email_tests.py @@ -27,3 +27,9 @@ def test_get_object(self): self.manager.get_instance(1232123) self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', 'getObject') + + def test_update_email_address(self): + self.manager = EmailManager(self.client) + self.manager.update_email(1232123, 'test@ibm.com') + self.assert_called_with('SoftLayer_Network_Message_Delivery_Email_Sendgrid', + 'updateEmailAddress') From d92a1723f77eacc1f91707ffc4019b4ce1e1af77 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 24 May 2021 16:22:42 -0500 Subject: [PATCH 0663/1582] changed a testing domain to one that really doesnt exist --- SoftLayer/transports.py | 7 +++--- tests/transport_tests.py | 49 ++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index d0afe3d3b..bd643b568 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -395,7 +395,8 @@ def __call__(self, request): try: result = json.loads(resp.text) except ValueError as json_ex: - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, str(resp.text)) else: raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") @@ -413,8 +414,8 @@ def __call__(self, request): except ValueError as json_ex: if ex.response.text == "": raise exceptions.SoftLayerAPIError(resp.status_code, "Empty response.") - - raise exceptions.SoftLayerAPIError(resp.status_code, str(json_ex)) + LOGGER.warning(json_ex) + raise exceptions.SoftLayerAPIError(resp.status_code, ex.response.text) raise exceptions.SoftLayerAPIError(ex.response.status_code, message) except requests.RequestException as ex: diff --git a/tests/transport_tests.py b/tests/transport_tests.py index ca3dcc73b..c23f1054f 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -40,7 +40,7 @@ class TestXmlRpcAPICall(testing.TestCase): def set_up(self): self.transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) self.response = get_xmlrpc_response() @@ -71,7 +71,7 @@ def test_call(self, request): resp = self.transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -123,7 +123,7 @@ def test_identifier(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.identifier = 1234 @@ -141,7 +141,7 @@ def test_filter(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.filter = {'TYPE': {'attribute': {'operation': '^= prefix'}}} @@ -159,7 +159,7 @@ def test_limit_offset(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.limit = 10 @@ -179,7 +179,7 @@ def test_old_mask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = {"something": "nested"} @@ -201,7 +201,7 @@ def test_mask_call_no_mask_prefix(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "something.nested" @@ -217,7 +217,7 @@ def test_mask_call_v2(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask[something[nested]]" @@ -233,7 +233,7 @@ def test_mask_call_filteredMask(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "filteredMask[something[nested]]" @@ -249,7 +249,7 @@ def test_mask_call_v2_dot(self, request): request.return_value = self.response req = transports.Request() - req.endpoint = "http://something.com" + req.endpoint = "http://something9999999999999999999999.com" req.service = "SoftLayer_Service" req.method = "getObject" req.mask = "mask.something.nested" @@ -313,7 +313,7 @@ def test_ibm_id_call(self, auth, request): auth.assert_called_with('apikey', '1234567890qweasdzxc') request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', headers={'Content-Type': 'application/xml', 'User-Agent': consts.USER_AGENT}, proxies=None, @@ -385,7 +385,7 @@ def test_verify(request, request.return_value = get_xmlrpc_response() transport = transports.XmlRpcTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) req = transports.Request() @@ -401,7 +401,7 @@ def test_verify(request, transport(req) request.assert_called_with('POST', - 'http://something.com/SoftLayer_Service', + 'http://something9999999999999999999999.com/SoftLayer_Service', data=mock.ANY, headers=mock.ANY, cert=mock.ANY, @@ -415,7 +415,7 @@ class TestRestAPICall(testing.TestCase): def set_up(self): self.transport = transports.RestTransport( - endpoint_url='http://something.com', + endpoint_url='http://something9999999999999999999999.com', ) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -435,7 +435,7 @@ def test_basic(self, request): self.assertIsInstance(resp, transports.SoftLayerListResult) self.assertEqual(resp.total_count, 10) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', headers=mock.ANY, auth=None, data=None, @@ -501,7 +501,6 @@ def test_proxy_without_protocol(self): req.service = 'SoftLayer_Service' req.method = 'Resource' req.proxy = 'localhost:3128' - try: self.assertRaises(SoftLayer.TransportError, self.transport, req) except AssertionError: @@ -519,7 +518,7 @@ def test_valid_proxy(self, request): self.transport(req) request.assert_called_with( - 'GET', 'http://something.com/SoftLayer_Service/Resource.json', + 'GET', 'http://something9999999999999999999999.com/SoftLayer_Service/Resource.json', proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, auth=None, @@ -544,7 +543,7 @@ def test_with_id(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -568,7 +567,7 @@ def test_with_args(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", 1]}', @@ -592,7 +591,7 @@ def test_with_args_bytes(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'POST', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', headers=mock.ANY, auth=None, data='{"parameters": ["test", "YXNkZg=="]}', @@ -616,7 +615,7 @@ def test_with_filter(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectFilter': '{"TYPE": {"attribute": {"operation": "^= prefix"}}}'}, headers=mock.ANY, @@ -641,7 +640,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -662,7 +661,7 @@ def test_with_mask(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/getObject.json', params={'objectMask': 'mask[id,property]'}, headers=mock.ANY, auth=None, @@ -688,7 +687,7 @@ def test_with_limit_offset(self, request): self.assertEqual(resp, {}) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=None, data=None, @@ -731,7 +730,7 @@ def test_with_special_auth(self, auth, request): auth.assert_called_with(user, password) request.assert_called_with( 'GET', - 'http://something.com/SoftLayer_Service/2/getObject.json', + 'http://something9999999999999999999999.com/SoftLayer_Service/2/getObject.json', headers=mock.ANY, auth=mock.ANY, data=None, From 2a8637ac677498ecd728aaf1fcc3717387425951 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 24 May 2021 20:44:48 -0400 Subject: [PATCH 0664/1582] fix team code review --- SoftLayer/CLI/email/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/email/list.py b/SoftLayer/CLI/email/list.py index 87912b37f..d2042b350 100644 --- a/SoftLayer/CLI/email/list.py +++ b/SoftLayer/CLI/email/list.py @@ -1,4 +1,4 @@ -"""Get lists Email Delivery account Service """ +"""Lists Email Delivery Service """ # :license: MIT, see LICENSE for more details. import click From 668035f02f2f477bfecd5eac59526d7dee710e59 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 16:48:20 -0500 Subject: [PATCH 0665/1582] v5.9.5 updates --- CHANGELOG.md | 14 ++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f55340957..4e70595fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log + +## [5.9.4] - 2021-04-24 +https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 + +#### Improvements +- Changed a testing domain to one that really doesnt exist #1492 +- Fix Incomplete notes field for file and block #1484 +- Show component versions on hw detail #1470 +- Add the firewall information on slcli firewall detail #1475 +- Add an --orderBy parameters to call-api #1459 +- Add image detail transaction data #1479 + + + ## [5.9.4] - 2021-04-27 https://github.com/softlayer/softlayer-python/compare/v5.9.3...v5.9.4 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index cec2831e0..26a00c4cd 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.4' +VERSION = 'v5.9.5' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 48c1e4e6c..c2e70f61a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.4', + version='5.9.5', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 66a0908452f03c2980b93db04b34fba1eeebb356 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 25 May 2021 21:05:39 -0500 Subject: [PATCH 0666/1582] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e70595fa..0a79b6d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [5.9.4] - 2021-04-24 +## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 #### Improvements From a88f14761caf134d374f01d2e2b08bd4aec0143c Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:24:22 -0500 Subject: [PATCH 0667/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a318890e6..c4a0daeeb 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,7 +6,7 @@ description: | license: MIT -base: core18 +base: core20 grade: stable confinement: strict @@ -25,7 +25,6 @@ parts: source: https://github.com/softlayer/softlayer-python source-type: git plugin: python - python-version: python3 override-pull: | snapcraftctl pull snapcraftctl set-version "$(git describe --tags | sed 's/^v//')" @@ -34,4 +33,4 @@ parts: - python3 stage-packages: - - python3 \ No newline at end of file + - python3 From 8fd0e86c91f9a6aacf58db9712eacbef7abf1ceb Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:28:04 -0500 Subject: [PATCH 0668/1582] Update snapcraft.yaml --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c4a0daeeb..f8e2e2267 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -12,7 +12,7 @@ confinement: strict apps: slcli: - command: slcli + command: bin/slcli environment: LC_ALL: C.UTF-8 plugs: From 79a06c38bb48bb4d9712fec2d50ec26a7b2e2d72 Mon Sep 17 00:00:00 2001 From: kz6fittycent Date: Wed, 26 May 2021 12:34:47 -0500 Subject: [PATCH 0669/1582] Update README.rst --- README.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 2ae928347..fc05a3503 100644 --- a/README.rst +++ b/README.rst @@ -60,7 +60,12 @@ To install the slcli snap: .. code-block:: bash - $ sudo snap install slcli + $ sudo snap install slcli + + (or to get the latest release) + + $ sudo snap install slcli --edge + From b97540bf0eb7bbd9c16d3a745cbf3a5b7d9aa238 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 27 May 2021 17:32:58 -0400 Subject: [PATCH 0670/1582] slcli vlan cancel should report if a vlan is automatic --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/cancel.py | 30 ++++++++++++++++++++ SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 12 +++++++- SoftLayer/managers/billing.py | 27 ++++++++++++++++++ SoftLayer/managers/network.py | 8 ++++++ docs/api/managers/billing.rst | 5 ++++ docs/cli/vlan.rst | 4 +++ tests/CLI/modules/vlan_tests.py | 12 ++++++++ 8 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/vlan/cancel.py create mode 100644 SoftLayer/managers/billing.py create mode 100644 docs/api/managers/billing.rst diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..6672477ed 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -336,6 +336,7 @@ ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), + ('vlan:cancel', 'SoftLayer.CLI.vlan.cancel:cli'), ('summary', 'SoftLayer.CLI.summary:cli'), diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py new file mode 100644 index 000000000..7bef5e458 --- /dev/null +++ b/SoftLayer/CLI/vlan/cancel.py @@ -0,0 +1,30 @@ +"""Cancel Network Vlan.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.managers.billing import BillingManager + + +@click.command() +@click.argument('identifier') +@environment.pass_env +def cli(env, identifier): + """Cancel network vlan.""" + + mgr = SoftLayer.NetworkManager(env.client) + billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): + raise exceptions.CLIAbort('Aborted') + + item = mgr.get_vlan(identifier).get('billingItem') + if item: + billing.cancel_item(item.get('id'), 'cancel by cli command') + env.fout('Cancel Successfully') + else: + res = mgr.get_cancel_failure_reasons(identifier) + raise exceptions.ArgumentError(res) diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 758fe3b39..4e1c100ad 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -5,9 +5,19 @@ }, 'id': 1234, 'vlanNumber': 4444, - 'firewallInterfaces': None + 'firewallInterfaces': None, + 'billingItem': { + 'allowCancellationFlag': 1, + 'categoryCode': 'network_vlan', + 'description': 'Private Network Vlan', + 'id': 235689, + 'notes': 'test cli', + 'orderItemId': 147258, + } } editObject = True setTags = True getList = [getObject] + +cancel = True diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py new file mode 100644 index 000000000..bb2c11e23 --- /dev/null +++ b/SoftLayer/managers/billing.py @@ -0,0 +1,27 @@ +""" + SoftLayer.BillingItem + ~~~~~~~~~~~~~~~~~~~ + BillingItem manager + + :license: MIT, see LICENSE for more details. +""" + + +class BillingManager(object): + """Manager for interacting with Billing item instances.""" + + def __init__(self, client): + self.client = client + + def cancel_item(self, identifier, reason_cancel): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + True, + reason_cancel, + reason_cancel, + id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d609de5d5..50429cbb1 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -49,6 +49,7 @@ 'primaryRouter[id, fullyQualifiedDomainName, datacenter]', 'totalPrimaryIpAddressCount', 'networkSpace', + 'billingItem', 'hardware', 'subnets', 'virtualGuests', @@ -752,3 +753,10 @@ def set_subnet_ipddress_note(self, identifier, note): """ result = self.client.call('SoftLayer_Network_Subnet_IpAddress', 'editObject', note, id=identifier) return result + + def get_cancel_failure_reasons(self, identifier): + """get the reasons by cannot cancel the VLAN + + :param integer identifier: the instance ID + """ + return self.vlan.getCancelFailureReasons(id=identifier) diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst new file mode 100644 index 000000000..f44a9333c --- /dev/null +++ b/docs/api/managers/billing.rst @@ -0,0 +1,5 @@ +.. _billing: + +.. automodule:: SoftLayer.managers.billing + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..57a58932a 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -14,3 +14,7 @@ VLANs .. click:: SoftLayer.CLI.vlan.list:cli :prog: vlan list :show-nested: + +.. click:: SoftLayer.CLI.vlan.cancel:cli + :prog: vlan cancel + :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..59de39ad3 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -99,3 +99,15 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assert_no_fail(result) + + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_fail(self, confirm_mock): + confirm_mock.return_value = False + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) From 03583ee7adb275d211a7c8a137d64b153c21186f Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 20:42:31 -0400 Subject: [PATCH 0671/1582] Refactor hw upgrade. --- SoftLayer/CLI/hardware/upgrade.py | 32 +++++++++---------- SoftLayer/fixtures/SoftLayer_Product_Order.py | 10 +++--- SoftLayer/managers/hardware.py | 3 +- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index 89b89859e..d9e0c9487 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -68,8 +68,8 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad if test: add_data_to_table(response, table) else: - table.add_row(['order_date', response.get('orderDate')]) - table.add_row(['order_id', response.get('orderId')]) + table.add_row(['Order Date', response.get('orderDate')]) + table.add_row(['Order Id', response.get('orderId')]) add_data_to_table(response['orderDetails'], table) place_order_table = get_place_order_information(response) table.add_row(['Place Order Information', place_order_table]) @@ -83,21 +83,21 @@ def add_data_to_table(response, table): """Add the hardware server upgrade result to the table""" table.add_row(['location', utils.lookup(response, 'locationObject', 'longName')]) table.add_row(['quantity', response.get('quantity')]) - table.add_row(['package_id', response.get('packageId')]) - table.add_row(['currency_short_name', response.get('currencyShortName')]) - table.add_row(['prorated_initial_charge', response.get('proratedInitialCharge')]) - table.add_row(['prorated_order_total', response.get('proratedOrderTotal')]) - table.add_row(['use_hourly_pricing', response.get('useHourlyPricing')]) + table.add_row(['Package Id', response.get('packageId')]) + table.add_row(['Currency Short Name', response.get('currencyShortName')]) + table.add_row(['Prorated Initial Charge', response.get('proratedInitialCharge')]) + table.add_row(['Prorated Order Total', response.get('proratedOrderTotal')]) + table.add_row(['Hourly Pricing', response.get('useHourlyPricing')]) table_hardware = get_hardware_detail(response) table.add_row(['Hardware', table_hardware]) table_prices = get_hardware_prices(response) - table.add_row(['prices', table_prices]) + table.add_row(['Prices', table_prices]) def get_place_order_information(response): """Get the hardware server place order information.""" - table_place_order = formatting.Table(['id', 'account_id', 'status', 'Account CompanyName', - 'UserRecord FirstName', 'UserRecord lastName', 'UserRecord Username']) + table_place_order = formatting.Table(['Id', 'Account Id', 'Status', 'Account CompanyName', + 'UserRecord FirstName', 'UserRecord LastName', 'UserRecord Username']) table_place_order.add_row([response.get('id'), response.get('accountId'), response.get('status'), @@ -111,8 +111,8 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" - table_hardware = formatting.Table(['account_id', 'hostname', 'domain']) - for hardware in response['hardware']: + table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) + for hardware in response['Hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) @@ -122,7 +122,7 @@ def get_hardware_detail(response): def get_hardware_prices(response): """Get the hardware server prices.""" - table_prices = formatting.Table(['id', 'hourlyRecurringFee', 'recurringFee', 'categories', 'Item Description', + table_prices = formatting.Table(['Id', 'HourlyRecurringFee', 'RecurringFee', 'Categories', 'Item Description', 'Item Units']) for price in response['prices']: categories = price.get('categories')[0] @@ -138,9 +138,9 @@ def get_hardware_prices(response): def get_order_detail(response): """Get the hardware server order detail.""" - table_order_detail = formatting.Table(['billing_city', 'billing_country_code', 'billing_email', - 'billing_name_first', 'billing_name_last', 'billing_postal_code', - 'billing_state']) + table_order_detail = formatting.Table(['Billing City', 'Billing Country Code', 'Billing Email', + 'Billing Name First', 'Billing Name Last', 'Billing Postal Code', + 'Billing State']) table_order_detail.add_row([utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCity'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingCountryCode'), utils.lookup(response, 'orderDetails', 'billingInformation', 'billingEmail'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 83604f8d1..be702ccba 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -69,7 +69,7 @@ { "accountId": 1111, "domain": "testedit.com", - "hostname": "bardcabero", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-12312asdasdasd" } ], @@ -135,8 +135,8 @@ "accountId": 1111111, "bareMetalInstanceFlag": 0, "domain": "testedit.com", - "fullyQualifiedDomainName": "bardcabero.testedit.com", - "hostname": "bardcabero", + "fullyQualifiedDomainName": "test.testedit.com", + "hostname": "test", "globalIdentifier": "81434794-af69-44d5-bb97-1111111" } ], @@ -205,8 +205,8 @@ "accountId": 1234, "firstName": "test", "id": 3333, - "lastName": "cabero", - "username": "sl1234-dcabero" + "lastName": "test", + "username": "sl1234-test" } } } diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index fd14549ce..5bfd50869 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -908,7 +908,8 @@ def get_maintenance_windows_id(self, location_id): result_windows = self.client['SoftLayer_Provisioning_Maintenance_Window'].getMaintenanceWindows(begin_date, end_date, location_id) - return result_windows[0]['id'] + if len(result_windows) > 0: + return result_windows[0].get('id') @retry(logger=LOGGER) def get_instance(self, instance_id): From 0ad4846fa9a781d60efe923c22f6553c870f55b3 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 27 May 2021 21:05:38 -0400 Subject: [PATCH 0672/1582] Fix unit test and tox analysis. --- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/managers/hardware.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index d9e0c9487..a5b432e82 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -112,7 +112,7 @@ def get_place_order_information(response): def get_hardware_detail(response): """Get the hardware server detail.""" table_hardware = formatting.Table(['Account Id', 'Hostname', 'Domain']) - for hardware in response['Hardware']: + for hardware in response['hardware']: table_hardware.add_row([hardware.get('accountId'), hardware.get('hostname'), hardware.get('domain')]) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 5bfd50869..d7e65694e 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -835,6 +835,7 @@ def upgrade(self, instance_id, memory=None, :returns: bool """ result = None + maintenance_window_id = None upgrade_prices = self._get_upgrade_prices(instance_id) prices = [] data = {} @@ -853,7 +854,9 @@ def upgrade(self, instance_id, memory=None, location_id = server_response['datacenter']['id'] maintenance_window = datetime.datetime.now(utils.UTC()) - maintenance_window_id = self.get_maintenance_windows_id(location_id) + maintenance_window_detail = self.get_maintenance_windows_detail(location_id) + if maintenance_window_detail: + maintenance_window_id = maintenance_window_detail.get('id') order = { 'complexType': 'SoftLayer_Container_Product_Order_Hardware_Server_Upgrade', @@ -894,12 +897,13 @@ def upgrade(self, instance_id, memory=None, result = self.client['Product_Order'].placeOrder(order) return result - def get_maintenance_windows_id(self, location_id): + def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. :return int. """ + result = None begin_date_object = datetime.datetime.now() begin_date = begin_date_object.strftime("%Y-%m-%dT00:00:00.0000-06:00") end_date_object = datetime.date.today() + datetime.timedelta(days=30) @@ -909,7 +913,9 @@ def get_maintenance_windows_id(self, location_id): end_date, location_id) if len(result_windows) > 0: - return result_windows[0].get('id') + result = result_windows[0] + + return result @retry(logger=LOGGER) def get_instance(self, instance_id): From 773f4d59d37407566c8d446dbcc1bbb2b64f6a35 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 1 Jun 2021 11:25:16 -0400 Subject: [PATCH 0673/1582] Remove block/file interval option for replica volume. --- SoftLayer/CLI/block/replication/order.py | 15 +++++++++------ SoftLayer/CLI/file/replication/order.py | 15 +++++++++------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/SoftLayer/CLI/block/replication/order.py b/SoftLayer/CLI/block/replication/order.py index 743c91c0e..5324dfc19 100644 --- a/SoftLayer/CLI/block/replication/order.py +++ b/SoftLayer/CLI/block/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -40,13 +42,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): """Order a block storage replica volume.""" block_manager = SoftLayer.BlockStorageManager(env.client) + block_volume_id = helpers.resolve_id(block_manager.resolve_ids, volume_id, 'Block Volume') if tier is not None: tier = float(tier) try: order = block_manager.order_replicant_volume( - volume_id, + block_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -57,9 +60,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier, os_type): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") diff --git a/SoftLayer/CLI/file/replication/order.py b/SoftLayer/CLI/file/replication/order.py index 9ba2c84f8..2961cbd64 100644 --- a/SoftLayer/CLI/file/replication/order.py +++ b/SoftLayer/CLI/file/replication/order.py @@ -5,6 +5,8 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions +from SoftLayer.CLI import helpers +from SoftLayer import utils CONTEXT_SETTINGS = {'token_normalize_func': lambda x: x.upper()} @@ -14,9 +16,9 @@ @click.argument('volume_id') @click.option('--snapshot-schedule', '-s', help='Snapshot schedule to use for replication, ' - '(INTERVAL | HOURLY | DAILY | WEEKLY)', + '(HOURLY | DAILY | WEEKLY)', required=True, - type=click.Choice(['INTERVAL', 'HOURLY', 'DAILY', 'WEEKLY'])) + type=click.Choice(['HOURLY', 'DAILY', 'WEEKLY'])) @click.option('--location', '-l', help='Short name of the data center for the replicant ' '(e.g.: dal09)', @@ -29,13 +31,14 @@ def cli(env, volume_id, snapshot_schedule, location, tier): """Order a file storage replica volume.""" file_manager = SoftLayer.FileStorageManager(env.client) + file_volume_id = helpers.resolve_id(file_manager.resolve_ids, volume_id, 'File Storage') if tier is not None: tier = float(tier) try: order = file_manager.order_replicant_volume( - volume_id, + file_volume_id, snapshot_schedule=snapshot_schedule, location=location, tier=tier, @@ -45,9 +48,9 @@ def cli(env, volume_id, snapshot_schedule, location, tier): if 'placedOrder' in order.keys(): click.echo("Order #{0} placed successfully!".format( - order['placedOrder']['id'])) - for item in order['placedOrder']['items']: - click.echo(" > %s" % item['description']) + utils.lookup(order, 'placedOrder', 'id'))) + for item in utils.lookup(order, 'placedOrder', 'items'): + click.echo(" > %s" % item.get('description')) else: click.echo("Order could not be placed! Please verify your options " + "and try again.") From 7d2de3c5d37f29a89b2d40ec4f66c3cc3cbf64ac Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 3 Jun 2021 12:35:56 -0400 Subject: [PATCH 0674/1582] add new feature on vlan cli --- SoftLayer/CLI/routes.py | 1 + SoftLayer/CLI/vlan/create.py | 40 ++++++++++++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 16 +++++ .../fixtures/SoftLayer_Product_Package.py | 63 +++++++++++++++++++ docs/cli/vlan.rst | 4 ++ tests/CLI/modules/vlan_tests.py | 21 +++++++ 6 files changed, 145 insertions(+) create mode 100644 SoftLayer/CLI/vlan/create.py diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d5174ffde..5eb3a006b 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -333,6 +333,7 @@ ('user:vpn-subnet', 'SoftLayer.CLI.user.vpn_subnet:cli'), ('vlan', 'SoftLayer.CLI.vlan'), + ('vlan:create', 'SoftLayer.CLI.vlan.create:cli'), ('vlan:detail', 'SoftLayer.CLI.vlan.detail:cli'), ('vlan:edit', 'SoftLayer.CLI.vlan.edit:cli'), ('vlan:list', 'SoftLayer.CLI.vlan.list:cli'), diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py new file mode 100644 index 000000000..6d4a85273 --- /dev/null +++ b/SoftLayer/CLI/vlan/create.py @@ -0,0 +1,40 @@ +"""Order/create a dedicated server.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command(epilog="See 'slcli server create-options' for valid options.") +@click.option('--hostname', '-H', required=True, prompt=True, help="Host portion of the FQDN") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), + help='Network vlan type') +@click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), + help="Billing rate") +@environment.pass_env +def cli(env, hostname, datacenter, network, billing): + """Order/create a vlan instance.""" + + item_package = ['PUBLIC_NETWORK_VLAN'] + complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + if not network: + item_package = ['PRIVATE_NETWORK_VLAN'] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='NETWORK_VLAN', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=billing, + extras={'name': hostname}) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index 3774f63a8..df9747173 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -104,3 +104,19 @@ ] } } + +vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [{ + "id": 2018, + "itemId": 1071, + "categories": [{ + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}], + "item": { + "capacity": "0", + "description": "Public Network Vlan", + "id": 1071, + "keyName": "PUBLIC_NETWORK_VLAN"}} + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..54c7880c9 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,66 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItemsVLAN = [{ + "description": "Private Network Vlan", + "id": 1072, + "itemTaxCategoryId": 166, + "keyName": "PRIVATE_NETWORK_VLAN", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan"}, + "prices": [{ + "id": 203707, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 505, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203727, + "itemId": 1072, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +}, { + "description": "Public Network Vlan", + "id": 1071, + "itemTaxCategoryId": 166, + "keyName": "PUBLIC_NETWORK_VLAN", + "units": "N/A", + "itemCategory": { + "categoryCode": "network_vlan", + "id": 113, + "name": "Network Vlan", + }, + "prices": [{ + "id": 203637, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 509, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }, + { + "id": 203667, + "itemId": 1071, + "laborFee": "0", + "locationGroupId": 545, + "oneTimeFee": "0", + "recurringFee": "0", + "setupFee": "0", + "sort": 10, + }] +} +] diff --git a/docs/cli/vlan.rst b/docs/cli/vlan.rst index 6fc084da7..b68f3d26e 100644 --- a/docs/cli/vlan.rst +++ b/docs/cli/vlan.rst @@ -3,6 +3,10 @@ VLANs ===== +.. click:: SoftLayer.CLI.vlan.create:cli + :prog: vlan create + :show-nested: + .. click:: SoftLayer.CLI.vlan.detail:cli :prog: vlan detail :show-nested: diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 73c1fab97..5a1e7d4ae 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -4,8 +4,11 @@ :license: MIT, see LICENSE for more details. """ +import json from unittest import mock as mock +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package from SoftLayer import testing @@ -99,3 +102,21 @@ def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Account', 'getNetworkVlans') + + def test_create_vlan(self): + amock = self.set_mock('SoftLayer_Product_Package', 'getItems') + amock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '-H test', + '-d TEST00', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) From 47d14d0918cfe16cc6c99d088b664c9b65fd46c4 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 4 Jun 2021 10:49:25 -0400 Subject: [PATCH 0675/1582] fix team code review comments --- SoftLayer/CLI/vlan/cancel.py | 17 +++++++----- SoftLayer/fixtures/SoftLayer_Network_Vlan.py | 5 ++++ SoftLayer/managers/billing.py | 27 -------------------- SoftLayer/managers/network.py | 16 +++++++++++- tests/CLI/modules/vlan_tests.py | 8 ++++++ 5 files changed, 39 insertions(+), 34 deletions(-) delete mode 100644 SoftLayer/managers/billing.py diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 7bef5e458..79647a7eb 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -7,7 +7,6 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting -from SoftLayer.managers.billing import BillingManager @click.command() @@ -17,14 +16,20 @@ def cli(env, identifier): """Cancel network vlan.""" mgr = SoftLayer.NetworkManager(env.client) - billing = BillingManager(env.client) + if not (env.skip_confirmations or formatting.no_going_back(identifier)): raise exceptions.CLIAbort('Aborted') + reasons = mgr.get_cancel_failure_reasons(identifier) + if len(reasons) > 0: + raise exceptions.CLIAbort(reasons) item = mgr.get_vlan(identifier).get('billingItem') if item: - billing.cancel_item(item.get('id'), 'cancel by cli command') - env.fout('Cancel Successfully') + mgr.cancel_item(item.get('id'), + True, + 'Cancel by cli command', + 'Cancel by cli command') else: - res = mgr.get_cancel_failure_reasons(identifier) - raise exceptions.ArgumentError(res) + raise exceptions.CLIAbort( + "VLAN is an automatically assigned and free of charge VLAN," + " it will automatically be removed from your account when it is empty") diff --git a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py index 4e1c100ad..960c98995 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Vlan.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Vlan.py @@ -21,3 +21,8 @@ getList = [getObject] cancel = True + +getCancelFailureReasons = [ + "1 bare metal server(s) still on the VLAN ", + "1 virtual guest(s) still on the VLAN " +] diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py deleted file mode 100644 index bb2c11e23..000000000 --- a/SoftLayer/managers/billing.py +++ /dev/null @@ -1,27 +0,0 @@ -""" - SoftLayer.BillingItem - ~~~~~~~~~~~~~~~~~~~ - BillingItem manager - - :license: MIT, see LICENSE for more details. -""" - - -class BillingManager(object): - """Manager for interacting with Billing item instances.""" - - def __init__(self, client): - self.client = client - - def cancel_item(self, identifier, reason_cancel): - """Cancel a billing item immediately, deleting all its data. - - :param integer identifier: the instance ID to cancel - :param string reason_cancel: reason cancel - """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - True, - True, - reason_cancel, - reason_cancel, - id=identifier) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 50429cbb1..7c86dda58 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -755,8 +755,22 @@ def set_subnet_ipddress_note(self, identifier, note): return result def get_cancel_failure_reasons(self, identifier): - """get the reasons by cannot cancel the VLAN + """get the reasons why we cannot cancel the VLAN. :param integer identifier: the instance ID """ return self.vlan.getCancelFailureReasons(id=identifier) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + True, + cancel_immediately, + reason_cancel, + customer_note, + id=identifier) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 59de39ad3..af2b22290 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -103,9 +103,17 @@ def test_vlan_list(self): @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True + mock = self.set_mock('SoftLayer_Network_Vlan', 'getCancelFailureReasons') + mock.return_value = [] result = self.run_command(['vlan', 'cancel', '1234']) self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') + def test_vlan_cancel_error(self, confirm_mock): + confirm_mock.return_value = True + result = self.run_command(['vlan', 'cancel', '1234']) + self.assertTrue(result.exit_code, 2) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel_fail(self, confirm_mock): confirm_mock.return_value = False From 643bae8b3239d60fea786043d0722e003276f107 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 10:57:38 -0400 Subject: [PATCH 0676/1582] Add slcli account licenses --- SoftLayer/CLI/account/licenses.py | 41 +++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Account.py | 107 ++++++++++++++++++++++++ SoftLayer/managers/account.py | 22 +++++ tests/CLI/modules/account_tests.py | 6 ++ tests/managers/account_tests.py | 8 ++ 6 files changed, 185 insertions(+) create mode 100644 SoftLayer/CLI/account/licenses.py diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py new file mode 100644 index 000000000..cdf6e177f --- /dev/null +++ b/SoftLayer/CLI/account/licenses.py @@ -0,0 +1,41 @@ +"""Show the all account licenses.""" +# :license: MIT, see LICENSE for more details. +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers.account import AccountManager + + +@click.command() +@environment.pass_env +def cli(env): + """return the control panel and VMWare licenses""" + + manager = AccountManager(env.client) + + panel_control = manager.get_active_virtual_licenses() + vmwares = manager.get_active_account_licenses() + + table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', + 'key', 'subnet', 'subnet notes']) + + table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', + 'manufacturer', 'requiredUser']) + for panel in panel_control: + table_panel.add_row([panel.get('id'), panel.get('ipAddress'), + utils.lookup(panel, 'softwareDescription', 'manufacturer'), + utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), + panel.get('key'), utils.lookup(panel, 'subnet', 'broadcastAddress'), + utils.lookup(panel, 'subnet', 'note')]) + + env.fout(table_panel) + for vmware in vmwares: + table_vmware.add_row([utils.lookup(vmware, 'softwareDescription', 'name'), + vmware.get('key'), vmware.get('capacity'), + utils.lookup(vmware, 'billingItem', 'description'), + utils.lookup(vmware, 'softwareDescription', 'manufacturer'), + utils.lookup(vmware, 'softwareDescription', 'requiredUser')]) + + env.fout(table_vmware) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..4a267b7a4 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -16,6 +16,7 @@ ('account:invoices', 'SoftLayer.CLI.account.invoices:cli'), ('account:events', 'SoftLayer.CLI.account.events:cli'), ('account:event-detail', 'SoftLayer.CLI.account.event_detail:cli'), + ('account:licenses', 'SoftLayer.CLI.account.licenses:cli'), ('account:summary', 'SoftLayer.CLI.account.summary:cli'), ('account:billing-items', 'SoftLayer.CLI.account.billing_items:cli'), ('account:item-detail', 'SoftLayer.CLI.account.item_detail:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 4cfafa30f..11d1b26d0 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1100,3 +1100,110 @@ "smtpAccess": "1" } ] + +getActiveAccountLicenses = [{ + "accountId": 123456, + "capacity": "4", + "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 741258963, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 963258741, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 15963, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } +}, + { + "accountId": 123456, + "capacity": "4", + "key": "4122M-ABXC05-K829T-098HP-00QJM", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "description": "vCenter Server Appliance 6.x", + "id": 36987456, + "laborFee": "0", + "laborFeeTaxRate": "0", + "oneTimeFee": "0", + "oneTimeFeeTaxRate": "0", + "orderItemId": 25839, + "recurringFee": "0", + "recurringFeeTaxRate": "0", + "recurringMonths": 1, + "serviceProviderId": 1, + "setupFee": "0", + "setupFeeTaxRate": "0" + }, + "softwareDescription": { + "controlPanel": 0, + "id": 1472, + "licenseTermValue": 0, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "operatingSystem": 0, + "version": "6.0", + "virtualLicense": 0, + "virtualizationPlatform": 0, + "requiredUser": "administrator@vsphere.local" + } + } +] + +getActiveVirtualLicenses = [{ + "id": 12345, + "ipAddress": "192.168.23.78", + "key": "PLSK.06866259.0000", + "billingItem": { + "categoryCode": "control_panel", + "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " + }, + "softwareDescription": { + "longDescription": "Plesk - Unlimited Domain w/ Power Pack for VPS 17.8.11 Linux", + "manufacturer": "Plesk", + "name": "Plesk - Unlimited Domain w/ Power Pack for VPS" + }, + "subnet": { + "broadcastAddress": "192.168.23.79", + "cidr": 28, + "gateway": "192.168.23.65", + "id": 1973163, + "isCustomerOwned": False, + "isCustomerRoutable": False, + "netmask": "255.255.255.240", + "networkIdentifier": "128.116.23.64", + "networkVlanId": 123456, + "note": "test note", + "sortOrder": "1", + "subnetType": "ADDITIONAL_PRIMARY", + "totalIpAddresses": "16", + "usableIpAddressCount": "13", + "version": 4 + } +}] diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index b7398076d..841f2e92d 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -303,3 +303,25 @@ def get_network_message_delivery_accounts(self): _mask = """vendor,type""" return self.client['SoftLayer_Account'].getNetworkMessageDeliveryAccounts(mask=_mask) + + def get_active_virtual_licenses(self): + """Gets all active virtual licenses account. + + :returns: active virtual licenses account + """ + + _mask = """billingItem[categoryCode,createDate,description], + key,id,ipAddress, + softwareDescription[longDescription,name,manufacturer], + subnet""" + + return self.client['SoftLayer_Account'].getActiveVirtualLicenses(mask=_mask) + + def get_active_account_licenses(self): + """Gets all active account licenses. + + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" + + return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index 9b88e3fae..fe9c11b0b 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -116,3 +116,9 @@ def test_acccount_order(self): result = self.run_command(['account', 'orders']) self.assert_no_fail(result) self.assert_called_with('SoftLayer_Billing_Order', 'getAllObjects') + + def test_acccount_licenses(self): + result = self.run_command(['account', 'licenses']) + self.assert_no_fail(result) + self.assert_called_with('SoftLayer_Account', 'getActiveVirtualLicenses') + self.assert_called_with('SoftLayer_Account', 'getActiveAccountLicenses') diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index c5d2edf95..26b5dadff 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -153,3 +153,11 @@ def test_get_item_details_with_invoice_item_id(self): def test_get_routers(self): self.manager.get_routers() self.assert_called_with("SoftLayer_Account", "getRouters") + + def test_get_active_account_licenses(self): + self.manager.get_active_account_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveAccountLicenses") + + def test_get_active_virtual_licenses(self): + self.manager.get_active_virtual_licenses() + self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") From d3e37906092e1351a0a629a3c95edcc29c9fd0eb Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 8 Jun 2021 11:01:08 -0400 Subject: [PATCH 0677/1582] add documentation --- docs/cli/account.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/cli/account.rst b/docs/cli/account.rst index 27b4198d5..719c44fde 100644 --- a/docs/cli/account.rst +++ b/docs/cli/account.rst @@ -38,4 +38,8 @@ Account Commands .. click:: SoftLayer.CLI.account.orders:cli :prog: account orders - :show-nested: \ No newline at end of file + :show-nested: + +.. click:: SoftLayer.CLI.account.licenses:cli + :prog: account licenses + :show-nested: From 0b0e2ef76dc5329314bf888a5b7e8a8f978e487a Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 11:14:51 -0400 Subject: [PATCH 0678/1582] fix the last code review comments --- docs/api/managers/billing.rst | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/api/managers/billing.rst diff --git a/docs/api/managers/billing.rst b/docs/api/managers/billing.rst deleted file mode 100644 index f44a9333c..000000000 --- a/docs/api/managers/billing.rst +++ /dev/null @@ -1,5 +0,0 @@ -.. _billing: - -.. automodule:: SoftLayer.managers.billing - :members: - :inherited-members: \ No newline at end of file From bee41a23aead3ab4a46e5ce7dc46a85ef4368dab Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 9 Jun 2021 14:24:21 -0400 Subject: [PATCH 0679/1582] fix the last code review comments --- SoftLayer/CLI/vlan/cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/cancel.py b/SoftLayer/CLI/vlan/cancel.py index 79647a7eb..35f5aa0f9 100644 --- a/SoftLayer/CLI/vlan/cancel.py +++ b/SoftLayer/CLI/vlan/cancel.py @@ -13,7 +13,7 @@ @click.argument('identifier') @environment.pass_env def cli(env, identifier): - """Cancel network vlan.""" + """Cancel network VLAN.""" mgr = SoftLayer.NetworkManager(env.client) From b62006f80e5d13e3598f0aa9a8fcdb402bbd961b Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 9 Jun 2021 15:19:53 -0400 Subject: [PATCH 0680/1582] Add the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 76 ++++++++++++++++ SoftLayer/CLI/routes.py | 1 + ...rk_CdnMarketplace_Configuration_Mapping.py | 21 +++++ SoftLayer/managers/cdn.py | 91 ++++++++++++++++++- docs/cli/cdn.rst | 4 + tests/CLI/modules/cdn_tests.py | 28 ++++++ tests/managers/cdn_tests.py | 36 ++++++++ 7 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 SoftLayer/CLI/cdn/edit.py diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py new file mode 100644 index 000000000..c6cb052cd --- /dev/null +++ b/SoftLayer/CLI/cdn/edit.py @@ -0,0 +1,76 @@ +"""Edit a CDN Account.""" +# :license: MIT, see LICENSE for more details. + +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('hostname') +@click.option('--header', '-H', + type=click.STRING, + help="Host header." + ) +@click.option('--http-port', '-t', + type=click.INT, + help="HTTP port." + ) +@click.option('--origin', '-o', + required=True, + type=click.STRING, + help="Origin server address." + ) +@click.option('--respect-headers', '-r', + type=click.Choice(['1', '0']), + help="Respect headers. The value 1 is On and 0 is Off." + ) +@click.option('--cache', '-c', multiple=True, type=str, + help="Cache key optimization. These are the valid options to choose: 'include-all', 'ignore-all', " + "'include-specified', 'ignore-specified'. If you select 'include-specified' or 'ignore-specified' " + "please add a description too using again --cache, " + "e.g --cache=include-specified --cache=description." + ) +@click.option('--performance-configuration', '-p', + type=click.Choice(['General web delivery', 'Large file optimization', 'Video on demand optimization']), + help="Optimize for, General web delivery', 'Large file optimization', 'Video on demand optimization', " + "the Dynamic content acceleration option is not added because this has a special configuration." + ) +@environment.pass_env +def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account.""" + + manager = SoftLayer.CDNManager(env.client) + + cache_result = {} + if cache: + if len(cache) > 1: + cache_result['cacheKeyQueryRule'] = cache[0] + cache_result['description'] = cache[1] + else: + cache_result['cacheKeyQueryRule'] = cache[0] + + cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + respect_headers=respect_headers, cache=cache_result, + performance_configuration=performance_configuration) + + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + + for cdn in cdn_result: + table.add_row(['Create Date', cdn.get('createDate')]) + table.add_row(['Header', cdn.get('header')]) + table.add_row(['Http Port', cdn.get('httpPort')]) + table.add_row(['Origin Type', cdn.get('originType')]) + table.add_row(['Performance Configuration', cdn.get('performanceConfiguration')]) + table.add_row(['Protocol', cdn.get('protocol')]) + table.add_row(['Respect Headers', cdn.get('respectHeaders')]) + table.add_row(['Unique Id', cdn.get('uniqueId')]) + table.add_row(['Vendor Name', cdn.get('vendorName')]) + table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['cname', cdn.get('cname')]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..05bbc997c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -61,6 +61,7 @@ ('cdn', 'SoftLayer.CLI.cdn'), ('cdn:detail', 'SoftLayer.CLI.cdn.detail:cli'), + ('cdn:edit', 'SoftLayer.CLI.cdn.edit:cli'), ('cdn:list', 'SoftLayer.CLI.cdn.list:cli'), ('cdn:origin-add', 'SoftLayer.CLI.cdn.origin_add:cli'), ('cdn:origin-list', 'SoftLayer.CLI.cdn.origin_list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py index 51950b919..dc3ca1789 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py +++ b/SoftLayer/fixtures/SoftLayer_Network_CdnMarketplace_Configuration_Mapping.py @@ -1,6 +1,8 @@ listDomainMappings = [ { + "cacheKeyQueryRule": "include-all", "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "createDate": "2020-09-29T15:19:01-06:00", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -17,6 +19,7 @@ listDomainMappingByUniqueId = [ { "cname": "cdnakauuiet7s6u6.cdnedge.bluemix.net", + "performanceConfiguration": "Large file optimization", "domain": "test.example.com", "header": "test.example.com", "httpPort": 80, @@ -29,3 +32,21 @@ "vendorName": "akamai" } ] + +updateDomainMapping = [ + { + "createDate": "2021-02-09T19:32:29-06:00", + "originType": "HOST_SERVER", + "path": "/*", + "performanceConfiguration": "Large file optimization", + "protocol": "HTTP", + "respectHeaders": True, + "uniqueId": "424406419091111", + "vendorName": "akamai", + "header": "www.test.com", + "httpPort": 83, + "cname": "cdn.test.cloud", + "originHost": "1.1.1.1", + "cacheKeyQueryRule": "include: test" + } +] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 2ead8c1fc..42acee390 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ - +import SoftLayer from SoftLayer import utils @@ -170,3 +170,92 @@ def start_data(self): def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date + + def edit(self, hostname, header=None, http_port=None, origin=None, + respect_headers=None, cache=None, performance_configuration=None): + """Edit the cdn object. + + :param string hostname: The CDN hostname. + :param header: The cdn Host header. + :param http_port: The cdn HTTP port. + :param origin: The cdn Origin server address. + :param respect_headers: The cdn Respect headers. + :param cache: The cdn Cache key optimization. + :param performance_configuration: The cdn performance configuration. + + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) + if cdn_instance_detail is None: + raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) + + unique_id = cdn_instance_detail.get('uniqueId') + + config = { + 'uniqueId': unique_id, + 'originType': cdn_instance_detail.get('originType'), + 'protocol': cdn_instance_detail.get('protocol'), + 'path': cdn_instance_detail.get('path'), + 'vendorName': cdn_instance_detail.get('vendorName'), + 'cname': cdn_instance_detail.get('cname'), + 'domain': cdn_instance_detail.get('domain'), + 'httpPort': cdn_instance_detail.get('httpPort') + } + + if header: + config['header'] = header + + if http_port: + config['httpPort'] = http_port + + if origin: + config['origin'] = origin + + if respect_headers: + config['respectHeaders'] = respect_headers + + if cache: + if 'include-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('include', cache) + config['cacheKeyQueryRule'] = cache_key_rule + elif 'ignore-specified' in cache['cacheKeyQueryRule']: + cache_key_rule = self.get_cache_key_query_rule('ignore', cache) + config['cacheKeyQueryRule'] = cache_key_rule + else: + config['cacheKeyQueryRule'] = cache['cacheKeyQueryRule'] + + if performance_configuration: + config['performanceConfiguration'] = performance_configuration + + return self.cdn_configuration.updateDomainMapping(config) + + def get_cdn_instance_by_hostname(self, hostname): + """Get the cdn object detail. + + :param string hostname: The CDN identifier. + :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. + """ + result = None + cdn_list = self.cdn_configuration.listDomainMappings() + for cdn in cdn_list: + if cdn.get('domain') == hostname: + result = cdn + break + + return result + + @staticmethod + def get_cache_key_query_rule(cache_type, cache): + """Get the cdn object detail. + + :param string cache_type: Cache type. + :param cache: Cache description. + + :return: string value. + """ + if 'description' not in cache: + raise SoftLayer.SoftLayerError('Please add a description to be able to update the' + ' cache.') + cache_result = '%s: %s' % (cache_type, cache['description']) + + return cache_result diff --git a/docs/cli/cdn.rst b/docs/cli/cdn.rst index e334cd6f3..3b749995c 100644 --- a/docs/cli/cdn.rst +++ b/docs/cli/cdn.rst @@ -27,3 +27,7 @@ Interacting with CDN .. click:: SoftLayer.CLI.cdn.purge:cli :prog: cdn purge :show-nested: + +.. click:: SoftLayer.CLI.cdn.edit:cli + :prog: cdn edit + :show-nested: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index cb3c59e43..2a1cf627a 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -95,3 +95,31 @@ def test_remove_origin(self): self.assert_no_fail(result) self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") + + def test_edit_header(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--header=www.test.com']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('www.test.com', header_result['Header']) + + def test_edit_http_port(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--http-port=83']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(83, header_result['Http Port']) + + def test_edit_respect_headers(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--respect-headers=1']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual(True, header_result['Respect Headers']) + + def test_edit_cache(self): + result = self.run_command(['cdn', 'edit', 'test.example.com', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['CacheKeyQueryRule']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index 32f9ea9e8..d7f76bbd9 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer import fixtures from SoftLayer.managers import cdn from SoftLayer import testing @@ -102,3 +103,38 @@ def test_purge_content(self): self.assert_called_with('SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge', 'createPurge', args=args) + + def test_cdn_edit(self): + hostname = 'test.example.com' + header = 'www.test.com' + origin = '1.1.1.1' + result = self.cdn_client.edit(hostname, header=header, origin=origin) + + self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. + updateDomainMapping, result) + + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'updateDomainMapping', + args=({ + 'uniqueId': '9934111111111', + 'originType': 'HOST_SERVER', + 'protocol': 'HTTP', + 'path': '/', + 'vendorName': 'akamai', + 'cname': 'cdnakauuiet7s6u6.cdnedge.bluemix.net', + 'domain': 'test.example.com', + 'httpPort': 80, + 'header': 'www.test.com', + 'origin': '1.1.1.1' + },) + ) + + def test_cdn_instance_by_hostname(self): + hostname = 'test.example.com' + result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings + self.assertEqual(expected_result[0], result) + self.assert_called_with( + 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', + 'listDomainMappings',) From a48c7c5aed9a33d8663b959456a51d64e025aa4c Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:46:00 -0400 Subject: [PATCH 0681/1582] create a new commands on slcli that create/cancel a VMware licenses simulate to IBMCloud portal --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/cancel.py | 37 ++++++++++++++ SoftLayer/CLI/licenses/create.py | 33 ++++++++++++ SoftLayer/CLI/routes.py | 4 ++ SoftLayer/fixtures/SoftLayer_Product_Order.py | 32 ++++++++++++ .../fixtures/SoftLayer_Product_Package.py | 26 ++++++++++ .../SoftLayer_Software_AccountLicense.py | 51 +++++++++++++++++++ SoftLayer/managers/license.py | 40 +++++++++++++++ docs/cli/licenses.rst | 12 +++++ tests/CLI/modules/licenses_test.py | 34 +++++++++++++ 10 files changed, 269 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/cancel.py create mode 100644 SoftLayer/CLI/licenses/create.py create mode 100644 SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py create mode 100644 SoftLayer/managers/license.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py new file mode 100644 index 000000000..2dc8e9d81 --- /dev/null +++ b/SoftLayer/CLI/licenses/cancel.py @@ -0,0 +1,37 @@ +"""Cancel VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click +from SoftLayer import utils + +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.managers.license import LicensesManager + + +@click.command() +@click.argument('key') +@click.option('--immediate', is_flag=True, help='Immediate cancellation') +@environment.pass_env +def cli(env, key, immediate): + """Cancel VMware license.""" + + if not immediate: + immediate = False + vmware_find = False + license = LicensesManager(env.client) + + vmware_licenses = license.get_all_objects() + + for vmware in vmware_licenses: + if vmware.get('key') == key: + vmware_find = True + license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + immediate, + 'Cancel by cli command', + 'Cancel by cli command') + break + + if not vmware_find: + raise exceptions.CLIAbort( + "The VMware not found, try whit another key") diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py new file mode 100644 index 000000000..7c66cacc8 --- /dev/null +++ b/SoftLayer/CLI/licenses/create.py @@ -0,0 +1,33 @@ +"""Order/create a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. + +import click +from SoftLayer.managers import ordering + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@environment.pass_env +def cli(env, key, datacenter): + """Order/create a vlan instance.""" + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + item_package = [key] + + ordering_manager = ordering.OrderingManager(env.client) + result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) + table = formatting.KeyValueTable(['name', 'value']) + table.align['name'] = 'r' + table.align['value'] = 'l' + table.add_row(['id', result['orderId']]) + table.add_row(['created', result['orderDate']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index 38a716434..b931bd3a6 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -217,6 +217,10 @@ ('nas:list', 'SoftLayer.CLI.nas.list:cli'), ('nas:credentials', 'SoftLayer.CLI.nas.credentials:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create', 'SoftLayer.CLI.licenses.create:cli'), + ('licenses:cancel', 'SoftLayer.CLI.licenses.cancel:cli'), + ('object-storage', 'SoftLayer.CLI.object_storage'), ('object-storage:accounts', 'SoftLayer.CLI.object_storage.list_accounts:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index be702ccba..2202f2501 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -252,3 +252,35 @@ ] } } + +wmware_placeOrder = { + "orderDate": "2021-06-02 15:23:47", + "orderId": 123456, + "prices": [ + { + "id": 176535, + "itemId": 8109, + "categories": [ + { + "categoryCode": "software_license", + "id": 438, + "name": "Software License" + } + ], + "item": { + "capacity": "1", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 8109, + "keyName": "VMWARE_VSAN_ADVANCE_TIER_III_64_124_6_X", + "softwareDescription": { + "id": 1795, + }, + "thirdPartyPolicyAssignments": [ + { + "id": 29263, + "policyName": "3rd Party Software Terms VMWare v4" + } + ] + } + } + ]} diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..2ec118f3a 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "oneTimeFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py new file mode 100644 index 000000000..b4433d104 --- /dev/null +++ b/SoftLayer/fixtures/SoftLayer_Software_AccountLicense.py @@ -0,0 +1,51 @@ +getAllObjects = [{ + "capacity": "4", + "key": "ABCDE-6CJ8L-J8R9H-000R0-CDR70", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2018-10-22T11:16:48-06:00", + "cycleStartDate": "2021-06-03T23:11:22-06:00", + "description": "vCenter Server Appliance 6.0", + "id": 123654789, + "lastBillDate": "2021-06-03T23:11:22-06:00", + "modifyDate": "2021-06-03T23:11:22-06:00", + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 385054741, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1529, + "longDescription": "VMware vCenter 6.0", + "manufacturer": "VMware", + "name": "vCenter", + "version": "6.0", + "requiredUser": "administrator@vsphere.local" + } + }, + { + "capacity": "1", + "key": "CBERT-4RL92-K8999-031K4-AJF5J", + "units": "CPU", + "billingItem": { + "allowCancellationFlag": 1, + "categoryCode": "software_license", + "createDate": "2021-06-09T14:51:38-06:00", + "cycleStartDate": "2021-06-09T14:51:38-06:00", + "description": "VMware vSAN Advanced Tier III 64 - 124 TB 6.x", + "id": 369852174, + "nextBillDate": "2021-07-03T23:00:00-06:00", + "orderItemId": 836502628, + "recurringMonths": 1, + "serviceProviderId": 1, + }, + "softwareDescription": { + "id": 1795, + "longDescription": "VMware Virtual SAN Advanced Tier III 6.2", + "manufacturer": "VMware", + "name": "Virtual SAN Advanced Tier III", + "version": "6.2", + } + }] diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py new file mode 100644 index 000000000..84e5e538f --- /dev/null +++ b/SoftLayer/managers/license.py @@ -0,0 +1,40 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_all_objects(self): + """Show the all VM ware licenses of account. + + """ + _mask = '''softwareDescription,billingItem''' + + return self.client.call('SoftLayer_Software_AccountLicense', + 'getAllObjects', mask=_mask) + + def cancel_item(self, identifier, cancel_immediately, + reason_cancel, customer_note): + """Cancel a billing item immediately, deleting all its data. + + :param integer identifier: the instance ID to cancel + :param string reason_cancel: reason cancel + """ + return self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + reason_cancel, + customer_note, + id=identifier) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..55ca0fe10 --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,12 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create:cli + :prog: licenses create + :show-nested: + +.. click:: SoftLayer.CLI.licenses.cancel:cli + :prog: licenses cancel + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b769e80bd --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,34 @@ +""" + SoftLayer.tests.CLI.modules.licenses_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" + +from SoftLayer.fixtures import SoftLayer_Product_Order +from SoftLayer.fixtures import SoftLayer_Product_Package + +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.wmware_placeOrder + result = self.run_command(['licenses', + 'create', + '-k', 'VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2', + '-d dal03']) + self.assert_no_fail(result) + + def test_cancel(self): + result = self.run_command(['licenses', + 'cancel', + 'ABCDE-6CJ8L-J8R9H-000R0-CDR70', + '--immediate']) + self.assert_no_fail(result) From 3e41e5b6ca4e9ac3ed55e7412379ba693b98acc9 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:51:49 -0400 Subject: [PATCH 0682/1582] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 2dc8e9d81..c5b5f4bbd 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,5 +1,5 @@ -"""Cancel VMware licenses.""" -# :license: MIT, see LICENSE for more details. +"""Cancel a vwmare licenses.""" +# :licenses: MIT, see LICENSE for more details. import click from SoftLayer import utils From 15749114ea1b13dccb7ed8233aa1293fff001e7f Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 14:58:11 -0400 Subject: [PATCH 0683/1582] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 2 +- SoftLayer/CLI/licenses/create.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index c5b5f4bbd..931685bda 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -14,7 +14,7 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware license.""" + """Cancel VMware licenses.""" if not immediate: immediate = False diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index 7c66cacc8..d3d779c96 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -13,7 +13,7 @@ @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a vlan instance.""" + """Order/create a Vm licenses instance.""" complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] From 4a15aad12ca62f7392cd4f95617dbf67e31085e2 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:04:04 -0400 Subject: [PATCH 0684/1582] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 931685bda..03d6bea1b 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -18,20 +18,20 @@ def cli(env, key, immediate): if not immediate: immediate = False - vmware_find = False - license = LicensesManager(env.client) + vm_ware_find = False + licenses = LicensesManager(env.client) - vmware_licenses = license.get_all_objects() + vm_ware_licenses = licenses.get_all_objects() - for vmware in vmware_licenses: - if vmware.get('key') == key: - vmware_find = True - license.cancel_item(utils.lookup(vmware, 'billingItem', 'id'), + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), immediate, 'Cancel by cli command', 'Cancel by cli command') break - if not vmware_find: + if not vm_ware_find: raise exceptions.CLIAbort( "The VMware not found, try whit another key") From 1417c26c06ba72ab4d88f86f791171aaa7784a2a Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 14 Jun 2021 15:06:17 -0400 Subject: [PATCH 0685/1582] fix the tox analysis --- SoftLayer/CLI/licenses/cancel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 03d6bea1b..8b7007319 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -27,9 +27,9 @@ def cli(env, key, immediate): if vm_ware.get('key') == key: vm_ware_find = True licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') + immediate, + 'Cancel by cli command', + 'Cancel by cli command') break if not vm_ware_find: From 5fbd5e4a90449a83d1d5fd564eddca1251170131 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 15 Jun 2021 17:59:49 -0400 Subject: [PATCH 0686/1582] new method to fix the table disconfigurate when the text data is very long --- SoftLayer/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 6b18b6570..b65d3634a 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -406,3 +406,24 @@ def trim_to(string, length=80, tail="..."): return string[:length] + tail else: return string + + +def format_comment(comment, max_line_length): + """Return a string that is length long, added a next line and keep the table format. + + :param string string: String you want to add next line + :param int length: max length for the string + """ + comment_length = 0 + words = comment.split(" ") + formatted_comment = "" + for word in words: + if comment_length + (len(word) + 1) <= max_line_length: + formatted_comment = formatted_comment + word + " " + + comment_length = comment_length + len(word) + 1 + else: + formatted_comment = formatted_comment + "\n" + word + " " + + comment_length = len(word) + 1 + return formatted_comment From 402308b6d2bc7d78dcadee5943a2a0d0935c8d1d Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:03:33 -0400 Subject: [PATCH 0687/1582] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 22c579ac7..ce46692d4 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '-H test', + '--name','test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From ec5efc11a403ad49459b1edd14b0f604d4d035de Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 09:09:48 -0400 Subject: [PATCH 0688/1582] fix tox tool --- tests/CLI/modules/vlan_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index ce46692d4..c1417a018 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -111,7 +111,7 @@ def test_create_vlan(self): order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder result = self.run_command(['vlan', 'create', - '--name','test', + '--name', 'test', '-d TEST00', '--network', 'public', '--billing', 'hourly' From e6feb690fda1a85933e74695c78ea515e9c654f8 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 16 Jun 2021 12:13:21 -0400 Subject: [PATCH 0689/1582] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 4 ++-- SoftLayer/fixtures/SoftLayer_Account.py | 6 +++--- SoftLayer/managers/account.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index cdf6e177f..ab607f4de 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,4 +1,4 @@ -"""Show the all account licenses.""" +"""Show all licenses.""" # :license: MIT, see LICENSE for more details. import click from SoftLayer import utils @@ -11,7 +11,7 @@ @click.command() @environment.pass_env def cli(env): - """return the control panel and VMWare licenses""" + """Show all licenses.""" manager = AccountManager(env.client) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 11d1b26d0..7ca2d7e67 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -1104,7 +1104,7 @@ getActiveAccountLicenses = [{ "accountId": 123456, "capacity": "4", - "key": "M02A5-6CJ8L-J8R9H-000R0-CDR70", + "key": "Y8GNS-7QRNG-OUIJO-MATEI-5GJRM", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1141,7 +1141,7 @@ { "accountId": 123456, "capacity": "4", - "key": "4122M-ABXC05-K829T-098HP-00QJM", + "key": "TSZES-SJF85-04GLD-AXA64-8O1EO", "units": "CPU", "billingItem": { "allowCancellationFlag": 1, @@ -1179,7 +1179,7 @@ getActiveVirtualLicenses = [{ "id": 12345, "ipAddress": "192.168.23.78", - "key": "PLSK.06866259.0000", + "key": "TEST.60220734.0000", "billingItem": { "categoryCode": "control_panel", "description": "Plesk Onyx (Linux) - (Unlimited) - VPS " diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index 841f2e92d..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -307,8 +307,8 @@ def get_network_message_delivery_accounts(self): def get_active_virtual_licenses(self): """Gets all active virtual licenses account. - :returns: active virtual licenses account - """ + :returns: active virtual licenses account + """ _mask = """billingItem[categoryCode,createDate,description], key,id,ipAddress, @@ -320,8 +320,9 @@ def get_active_virtual_licenses(self): def get_active_account_licenses(self): """Gets all active account licenses. - :returns: Active account Licenses - """ + :returns: Active account Licenses + """ + _mask = """billingItem,softwareDescription""" return self.client['SoftLayer_Account'].getActiveAccountLicenses(mask=_mask) From cba97f7e73ee84039de261c7f9fa1e3d4e4ad53f Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 2 Jun 2021 16:33:44 -0400 Subject: [PATCH 0690/1582] #1480 add gateway/firewall name to vlan detail and list command --- SoftLayer/CLI/vlan/detail.py | 37 +++++++++++++++++++++------------ SoftLayer/CLI/vlan/list.py | 15 ++++++------- SoftLayer/managers/network.py | 14 +++++++++++++ tests/CLI/modules/vlan_tests.py | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/SoftLayer/CLI/vlan/detail.py b/SoftLayer/CLI/vlan/detail.py index 6acfb50cb..323699139 100644 --- a/SoftLayer/CLI/vlan/detail.py +++ b/SoftLayer/CLI/vlan/detail.py @@ -7,6 +7,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting from SoftLayer.CLI import helpers +from SoftLayer import utils @click.command() @@ -30,26 +31,24 @@ def cli(env, identifier, no_vs, no_hardware): table.align['name'] = 'r' table.align['value'] = 'l' - table.add_row(['id', vlan['id']]) - table.add_row(['number', vlan['vlanNumber']]) + table.add_row(['id', vlan.get('id')]) + table.add_row(['number', vlan.get('vlanNumber')]) table.add_row(['datacenter', - vlan['primaryRouter']['datacenter']['longName']]) + utils.lookup(vlan, 'primaryRouter', 'datacenter', 'longName')]) table.add_row(['primary_router', - vlan['primaryRouter']['fullyQualifiedDomainName']]) - table.add_row(['firewall', - 'Yes' if vlan['firewallInterfaces'] else 'No']) + utils.lookup(vlan, 'primaryRouter', 'fullyQualifiedDomainName')]) + table.add_row(['Gateway/Firewall', get_gateway_firewall(vlan)]) subnets = [] for subnet in vlan.get('subnets', []): subnet_table = formatting.KeyValueTable(['name', 'value']) subnet_table.align['name'] = 'r' subnet_table.align['value'] = 'l' - subnet_table.add_row(['id', subnet['id']]) - subnet_table.add_row(['identifier', subnet['networkIdentifier']]) - subnet_table.add_row(['netmask', subnet['netmask']]) - subnet_table.add_row(['gateway', subnet.get('gateway', '-')]) - subnet_table.add_row(['type', subnet['subnetType']]) - subnet_table.add_row(['usable ips', - subnet['usableIpAddressCount']]) + subnet_table.add_row(['id', subnet.get('id')]) + subnet_table.add_row(['identifier', subnet.get('networkIdentifier')]) + subnet_table.add_row(['netmask', subnet.get('netmask')]) + subnet_table.add_row(['gateway', subnet.get('gateway', formatting.blank())]) + subnet_table.add_row(['type', subnet.get('subnetType')]) + subnet_table.add_row(['usable ips', subnet.get('usableIpAddressCount')]) subnets.append(subnet_table) table.add_row(['subnets', subnets]) @@ -81,3 +80,15 @@ def cli(env, identifier, no_vs, no_hardware): table.add_row(['hardware', 'none']) env.fout(table) + + +def get_gateway_firewall(vlan): + """Gets the name of a gateway/firewall from a VLAN. """ + + firewall = utils.lookup(vlan, 'networkVlanFirewall', 'fullyQualifiedDomainName') + if firewall: + return firewall + gateway = utils.lookup(vlan, 'attachedNetworkGateway', 'name') + if gateway: + return gateway + return formatting.blank() diff --git a/SoftLayer/CLI/vlan/list.py b/SoftLayer/CLI/vlan/list.py index 44500532a..acb4460e5 100644 --- a/SoftLayer/CLI/vlan/list.py +++ b/SoftLayer/CLI/vlan/list.py @@ -6,12 +6,13 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI.vlan.detail import get_gateway_firewall from SoftLayer import utils COLUMNS = ['id', 'number', 'name', - 'firewall', + 'Gateway/Firewall', 'datacenter', 'hardware', 'virtual_servers', @@ -45,14 +46,14 @@ def cli(env, sortby, datacenter, number, name, limit): limit=limit) for vlan in vlans: table.add_row([ - vlan['id'], - vlan['vlanNumber'], + vlan.get('id'), + vlan.get('vlanNumber'), vlan.get('name') or formatting.blank(), - 'Yes' if vlan['firewallInterfaces'] else 'No', + get_gateway_firewall(vlan), utils.lookup(vlan, 'primaryRouter', 'datacenter', 'name'), - vlan['hardwareCount'], - vlan['virtualGuestCount'], - vlan['totalPrimaryIpAddressCount'], + vlan.get('hardwareCount'), + vlan.get('virtualGuestCount'), + vlan.get('totalPrimaryIpAddressCount'), ]) env.fout(table) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..840d4f3b9 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -43,6 +43,8 @@ 'totalPrimaryIpAddressCount', 'virtualGuestCount', 'networkSpace', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) DEFAULT_GET_VLAN_MASK = ','.join([ 'firewallInterfaces', @@ -53,6 +55,8 @@ 'hardware', 'subnets', 'virtualGuests', + 'networkVlanFirewall[id,fullyQualifiedDomainName,primaryIpAddress]', + 'attachedNetworkGateway[id,name,networkFirewall]', ]) @@ -433,6 +437,16 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def get_network_gateway_firewall(self, vlan_id): + """Returns information about a single VLAN. + + :param int id: The unique identifier for the VLAN + :returns: A dictionary containing a large amount of information about + the specified VLAN. + + """ + return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) + def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index af2b22290..0ffb80165 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -95,6 +95,42 @@ def test_vlan_edit_failure(self, click): self.assert_no_fail(result) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject', identifier=100) + def test_vlan_detail_firewall(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'networkVlanFirewall': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + + def test_vlan_detail_gateway(self): + vlan_mock = self.set_mock('SoftLayer_Network_Vlan', 'getObject') + get_object = { + 'primaryRouter': { + 'datacenter': {'id': 1234, 'longName': 'TestDC'}, + 'fullyQualifiedDomainName': 'fcr01.TestDC' + }, + 'id': 1234, + 'vlanNumber': 4444, + 'attachedNetworkGateway': { + 'id': 54321, + "name": 'support' + }, + } + vlan_mock.return_value = get_object + result = self.run_command(['vlan', 'detail', '1234']) + self.assert_no_fail(result) + def test_vlan_list(self): result = self.run_command(['vlan', 'list']) self.assert_no_fail(result) From 6372aa2b3dd4792c4f201610cbca70c7c8ae947d Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 17 Jun 2021 20:22:51 -0400 Subject: [PATCH 0691/1582] #1480 remove duplicated method added on network manager --- SoftLayer/managers/network.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 840d4f3b9..21a4516ac 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -437,16 +437,6 @@ def get_vlan(self, vlan_id): """ return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def get_network_gateway_firewall(self, vlan_id): - """Returns information about a single VLAN. - - :param int id: The unique identifier for the VLAN - :returns: A dictionary containing a large amount of information about - the specified VLAN. - - """ - return self.vlan.getObject(id=vlan_id, mask=DEFAULT_GET_VLAN_MASK) - def list_global_ips(self, version=None, identifier=None, **kwargs): """Returns a list of all global IP address records on the account. From 13ae244d527187527f34a5c2df81eef1de71520d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:14:51 -0400 Subject: [PATCH 0692/1582] new feature licenses create-options --- SoftLayer/CLI/licenses/__init__.py | 0 SoftLayer/CLI/licenses/create_options.py | 28 +++++++++++++++++++ SoftLayer/CLI/routes.py | 3 ++ .../fixtures/SoftLayer_Product_Package.py | 26 +++++++++++++++++ SoftLayer/managers/licenses.py | 25 +++++++++++++++++ docs/cli/licenses.rst | 8 ++++++ tests/CLI/modules/licenses_test.py | 13 +++++++++ 7 files changed, 103 insertions(+) create mode 100644 SoftLayer/CLI/licenses/__init__.py create mode 100644 SoftLayer/CLI/licenses/create_options.py create mode 100644 SoftLayer/managers/licenses.py create mode 100644 docs/cli/licenses.rst create mode 100644 tests/CLI/modules/licenses_test.py diff --git a/SoftLayer/CLI/licenses/__init__.py b/SoftLayer/CLI/licenses/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py new file mode 100644 index 000000000..d4f027f3d --- /dev/null +++ b/SoftLayer/CLI/licenses/create_options.py @@ -0,0 +1,28 @@ +"""Licenses order options for a given VMware licenses.""" +# :license: MIT, see LICENSE for more details. + +import click + +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer.managers import licenses +from SoftLayer import utils + + +@click.command() +@environment.pass_env +def cli(env): + """Server order options for a given chassis.""" + + licenses_manager = licenses.LicensesManager(env.client) + + options = licenses_manager.get_create_options() + + table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + for item in options: + table.add_row([item.get('id'), + utils.trim_to(item.get('description'), 40), + item.get('keyName'), + item.get('prices')[0]['recurringFee']]) + + env.fout(table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index d6266f95b..9f9e7df6f 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -124,6 +124,9 @@ ('email:detail', 'SoftLayer.CLI.email.detail:cli'), ('email:edit', 'SoftLayer.CLI.email.edit:cli'), + ('licenses', 'SoftLayer.CLI.licenses'), + ('licenses:create-options', 'SoftLayer.CLI.licenses.create_options:cli'), + ('event-log', 'SoftLayer.CLI.event_log'), ('event-log:get', 'SoftLayer.CLI.event_log.get:cli'), ('event-log:types', 'SoftLayer.CLI.event_log.types:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Product_Package.py b/SoftLayer/fixtures/SoftLayer_Product_Package.py index f705b0edb..273ec9970 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Package.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Package.py @@ -2055,3 +2055,29 @@ "categoryCode": "dedicated_virtual_hosts" } }]} + +getItems_vmware = [{ + "capacity": "2", + "description": "VMware vSAN Enterprise Tier III 65 - 124 TB 6.x", + "id": 9567, + "itemTaxCategoryId": 166, + "keyName": "VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2", + "softwareDescriptionId": 1979, + "units": "CPU", + "itemCategory": { + "categoryCode": "software_license", + "id": 438, + "name": "Software License", + "quantityLimit": 1, + }, + "prices": [ + { + "id": 245164, + "itemId": 9567, + "laborFee": "0", + "locationGroupId": None, + "recurringFee": "0", + "setupFee": "0", + "sort": 0, + } + ]}] diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py new file mode 100644 index 000000000..3d6525382 --- /dev/null +++ b/SoftLayer/managers/licenses.py @@ -0,0 +1,25 @@ +""" + SoftLayer.license + ~~~~~~~~~~~~~~~ + License Manager + :license: MIT, see LICENSE for more details. +""" + + +# pylint: disable=too-many-public-methods + + +class LicensesManager(object): + """Manages account lincese.""" + + def __init__(self, client): + self.client = client + + def get_create_options(self): + """Returns valid options for ordering Licenses. + + :param string datacenter: short name, like dal09 + """ + + return self.client.call('SoftLayer_Product_Package', 'getItems', + id=301) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst new file mode 100644 index 000000000..8a0326d0e --- /dev/null +++ b/docs/cli/licenses.rst @@ -0,0 +1,8 @@ +.. _cli_licenses: + +licenses Commands +================= + +.. click:: SoftLayer.CLI.licenses.create-options:cli + :prog: licenses create-options + :show-nested: \ No newline at end of file diff --git a/tests/CLI/modules/licenses_test.py b/tests/CLI/modules/licenses_test.py new file mode 100644 index 000000000..b50b4edff --- /dev/null +++ b/tests/CLI/modules/licenses_test.py @@ -0,0 +1,13 @@ +from SoftLayer.fixtures import SoftLayer_Product_Package +from SoftLayer import testing + + +class LicensesTests(testing.TestCase): + + def test_create(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + + _mock.return_value = SoftLayer_Product_Package.getItems_vmware + + result = self.run_command(['licenses', 'create-options']) + self.assert_no_fail(result) From 9341bac077d365a0d5ff6a4f9fb1f61724fa165d Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 18 Jun 2021 15:28:52 -0400 Subject: [PATCH 0693/1582] fix the tox tool --- docs/cli/licenses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/licenses.rst b/docs/cli/licenses.rst index 8a0326d0e..b6d6fe66e 100644 --- a/docs/cli/licenses.rst +++ b/docs/cli/licenses.rst @@ -3,6 +3,6 @@ licenses Commands ================= -.. click:: SoftLayer.CLI.licenses.create-options:cli +.. click:: SoftLayer.CLI.licenses.create_options:cli :prog: licenses create-options :show-nested: \ No newline at end of file From d50b30cbbd9226f3b3c4bb093833b6ff961e53b0 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 22 Jun 2021 11:02:44 -0400 Subject: [PATCH 0694/1582] Fix the code review comments --- SoftLayer/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index b65d3634a..5bac80696 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -408,11 +408,11 @@ def trim_to(string, length=80, tail="..."): return string -def format_comment(comment, max_line_length): +def format_comment(comment, max_line_length=60): """Return a string that is length long, added a next line and keep the table format. - :param string string: String you want to add next line - :param int length: max length for the string + :param string comment: String you want to add next line + :param int max_line_length: max length for the string """ comment_length = 0 words = comment.split(" ") From c8033c5ab7fec80bffc2111a6898a7f71d1039d4 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 22 Jun 2021 15:31:31 -0400 Subject: [PATCH 0695/1582] Refactor and fix the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 15 ++++++++++----- SoftLayer/managers/cdn.py | 24 ++++++++++++------------ tests/CLI/modules/cdn_tests.py | 9 ++++++++- tests/managers/cdn_tests.py | 8 ++++---- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index c6cb052cd..677bcd4d7 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -6,10 +6,11 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers @click.command() -@click.argument('hostname') +@click.argument('identifier') @click.option('--header', '-H', type=click.STRING, help="Host header." @@ -39,10 +40,14 @@ "the Dynamic content acceleration option is not added because this has a special configuration." ) @environment.pass_env -def cli(env, hostname, header, http_port, origin, respect_headers, cache, performance_configuration): - """Edit a CDN Account.""" +def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): + """Edit a CDN Account. + + You can use the hostname or uniqueId as IDENTIFIER. + """ manager = SoftLayer.CDNManager(env.client) + cdn_id = helpers.resolve_id(manager.resolve_ids, identifier, 'CDN') cache_result = {} if cache: @@ -52,7 +57,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor else: cache_result['cacheKeyQueryRule'] = cache[0] - cdn_result = manager.edit(hostname, header=header, http_port=http_port, origin=origin, + cdn_result = manager.edit(cdn_id, header=header, http_port=http_port, origin=origin, respect_headers=respect_headers, cache=cache_result, performance_configuration=performance_configuration) @@ -70,7 +75,7 @@ def cli(env, hostname, header, http_port, origin, respect_headers, cache, perfor table.add_row(['Respect Headers', cdn.get('respectHeaders')]) table.add_row(['Unique Id', cdn.get('uniqueId')]) table.add_row(['Vendor Name', cdn.get('vendorName')]) - table.add_row(['CacheKeyQueryRule', cdn.get('cacheKeyQueryRule')]) + table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 42acee390..7189cbaa4 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -9,6 +9,9 @@ from SoftLayer import utils +# pylint: disable=no-self-use,too-many-lines,too-many-instance-attributes + + class CDNManager(utils.IdentifierMixin, object): """Manage Content Delivery Networks in the account. @@ -27,6 +30,7 @@ def __init__(self, client): self.cdn_path = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Mapping_Path'] self.cdn_metrics = self.client['Network_CdnMarketplace_Metrics'] self.cdn_purge = self.client['SoftLayer_Network_CdnMarketplace_Configuration_Cache_Purge'] + self.resolvers = [self._get_ids_from_hostname] def list_cdn(self, **kwargs): """Lists Content Delivery Networks for the active user. @@ -171,11 +175,11 @@ def end_date(self): """Retrieve the cdn usage metric end date.""" return self._end_date - def edit(self, hostname, header=None, http_port=None, origin=None, + def edit(self, identifier, header=None, http_port=None, origin=None, respect_headers=None, cache=None, performance_configuration=None): """Edit the cdn object. - :param string hostname: The CDN hostname. + :param string identifier: The CDN identifier. :param header: The cdn Host header. :param http_port: The cdn HTTP port. :param origin: The cdn Origin server address. @@ -185,14 +189,10 @@ def edit(self, hostname, header=None, http_port=None, origin=None, :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - cdn_instance_detail = self.get_cdn_instance_by_hostname(hostname) - if cdn_instance_detail is None: - raise SoftLayer.SoftLayerError('The CDN was not found with the hostname: %s' % hostname) - - unique_id = cdn_instance_detail.get('uniqueId') + cdn_instance_detail = self.get_cdn(str(identifier)) config = { - 'uniqueId': unique_id, + 'uniqueId': cdn_instance_detail.get('uniqueId'), 'originType': cdn_instance_detail.get('originType'), 'protocol': cdn_instance_detail.get('protocol'), 'path': cdn_instance_detail.get('path'), @@ -229,17 +229,17 @@ def edit(self, hostname, header=None, http_port=None, origin=None, return self.cdn_configuration.updateDomainMapping(config) - def get_cdn_instance_by_hostname(self, hostname): + def _get_ids_from_hostname(self, hostname): """Get the cdn object detail. :param string hostname: The CDN identifier. :returns: SoftLayer_Container_Network_CdnMarketplace_Configuration_Mapping[]. """ - result = None + result = [] cdn_list = self.cdn_configuration.listDomainMappings() for cdn in cdn_list: - if cdn.get('domain') == hostname: - result = cdn + if cdn.get('domain', '').lower() == hostname.lower(): + result.append(cdn.get('uniqueId')) break return result diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 2a1cf627a..5bff7e647 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -122,4 +122,11 @@ def test_edit_cache(self): '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) - self.assertEqual('include: test', header_result['CacheKeyQueryRule']) + self.assertEqual('include: test', header_result['Cache key optimization']) + + def test_edit_cache_by_uniqueId(self): + result = self.run_command(['cdn', 'edit', '9934111111111', + '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + self.assert_no_fail(result) + header_result = json.loads(result.output) + self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index d7f76bbd9..a57fe0b68 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -105,10 +105,10 @@ def test_purge_content(self): args=args) def test_cdn_edit(self): - hostname = 'test.example.com' + identifier = '9934111111111' header = 'www.test.com' origin = '1.1.1.1' - result = self.cdn_client.edit(hostname, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header, origin=origin) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) @@ -132,9 +132,9 @@ def test_cdn_edit(self): def test_cdn_instance_by_hostname(self): hostname = 'test.example.com' - result = self.cdn_client.get_cdn_instance_by_hostname(hostname) + result = self.cdn_client._get_ids_from_hostname(hostname) expected_result = fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping.listDomainMappings - self.assertEqual(expected_result[0], result) + self.assertEqual(expected_result[0]['uniqueId'], result[0]) self.assert_called_with( 'SoftLayer_Network_CdnMarketplace_Configuration_Mapping', 'listDomainMappings',) From 58736a0eb74d40ea06e768d3c4fdd747f97e3282 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 10:11:49 -0400 Subject: [PATCH 0696/1582] add the pod option --- SoftLayer/CLI/vlan/create.py | 9 +++++++-- tests/CLI/modules/vlan_tests.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1af33aec5..ad45735e7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -10,17 +10,22 @@ @click.command() @click.option('--name', required=False, prompt=True, help="Vlan name") -@click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") +@click.option('--datacenter', '-d', required=False, help="Datacenter shortname") +@click.option('--pod', '-p', required=False, help="Pod name. E.g dal05.pod01") @click.option('--network', default='public', show_default=True, type=click.Choice(['public', 'private']), help='Network vlan type') @click.option('--billing', default='hourly', show_default=True, type=click.Choice(['hourly', 'monthly']), help="Billing rate") @environment.pass_env -def cli(env, name, datacenter, network, billing): +def cli(env, name, datacenter, pod, network, billing): """Order/create a VLAN instance.""" item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' + + if pod and not datacenter: + datacenter = pod.split('.')[0] + if not network: item_package = ['PRIVATE_NETWORK_VLAN'] diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index c1417a018..86884dff6 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -121,6 +121,24 @@ def test_create_vlan(self): self.assertEqual(json.loads(result.output), {'id': 123456, 'created': '2021-06-02 15:23:47'}) + def test_create_vlan_pod(self): + _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') + _mock.return_value = SoftLayer_Product_Package.getItemsVLAN + + order_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder') + order_mock.return_value = SoftLayer_Product_Order.vlan_placeOrder + + result = self.run_command(['vlan', 'create', + '--name', 'test', + '-p TEST00.pod2', + '--network', 'public', + '--billing', 'hourly' + ]) + + self.assert_no_fail(result) + self.assertEqual(json.loads(result.output), + {'id': 123456, 'created': '2021-06-02 15:23:47'}) + @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): confirm_mock.return_value = True From 14f19da1be6fcdb8a3b1624833e8f29f7e1136e1 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 23 Jun 2021 15:15:33 -0400 Subject: [PATCH 0697/1582] fix the team code review comments --- SoftLayer/CLI/account/licenses.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/account/licenses.py b/SoftLayer/CLI/account/licenses.py index ab607f4de..42ddd9a8d 100644 --- a/SoftLayer/CLI/account/licenses.py +++ b/SoftLayer/CLI/account/licenses.py @@ -1,11 +1,12 @@ """Show all licenses.""" # :license: MIT, see LICENSE for more details. import click + from SoftLayer import utils from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers.account import AccountManager +from SoftLayer.managers import account @click.command() @@ -13,17 +14,17 @@ def cli(env): """Show all licenses.""" - manager = AccountManager(env.client) + manager = account.AccountManager(env.client) - panel_control = manager.get_active_virtual_licenses() + control_panel = manager.get_active_virtual_licenses() vmwares = manager.get_active_account_licenses() table_panel = formatting.KeyValueTable(['id', 'ip_address', 'manufacturer', 'software', - 'key', 'subnet', 'subnet notes']) + 'key', 'subnet', 'subnet notes'], title="Control Panel Licenses") table_vmware = formatting.KeyValueTable(['name', 'license_key', 'cpus', 'description', - 'manufacturer', 'requiredUser']) - for panel in panel_control: + 'manufacturer', 'requiredUser'], title="VMware Licenses") + for panel in control_panel: table_panel.add_row([panel.get('id'), panel.get('ipAddress'), utils.lookup(panel, 'softwareDescription', 'manufacturer'), utils.trim_to(utils.lookup(panel, 'softwareDescription', 'longDescription'), 40), From bcd52a696c4a040bf03ace6aefbe03da36b6b920 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 17:44:41 -0400 Subject: [PATCH 0698/1582] fix the team code review comments --- SoftLayer/CLI/licenses/cancel.py | 28 ++++----------------- SoftLayer/CLI/licenses/create.py | 21 ++++++++-------- SoftLayer/managers/__init__.py | 2 ++ SoftLayer/managers/license.py | 43 ++++++++++++++++++++++++-------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/SoftLayer/CLI/licenses/cancel.py b/SoftLayer/CLI/licenses/cancel.py index 8b7007319..783ce2857 100644 --- a/SoftLayer/CLI/licenses/cancel.py +++ b/SoftLayer/CLI/licenses/cancel.py @@ -1,12 +1,10 @@ -"""Cancel a vwmare licenses.""" +"""Cancel a license.""" # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer import utils +import SoftLayer from SoftLayer.CLI import environment -from SoftLayer.CLI import exceptions -from SoftLayer.managers.license import LicensesManager @click.command() @@ -14,24 +12,8 @@ @click.option('--immediate', is_flag=True, help='Immediate cancellation') @environment.pass_env def cli(env, key, immediate): - """Cancel VMware licenses.""" + """Cancel a license.""" - if not immediate: - immediate = False - vm_ware_find = False - licenses = LicensesManager(env.client) + licenses = SoftLayer.LicensesManager(env.client) - vm_ware_licenses = licenses.get_all_objects() - - for vm_ware in vm_ware_licenses: - if vm_ware.get('key') == key: - vm_ware_find = True - licenses.cancel_item(utils.lookup(vm_ware, 'billingItem', 'id'), - immediate, - 'Cancel by cli command', - 'Cancel by cli command') - break - - if not vm_ware_find: - raise exceptions.CLIAbort( - "The VMware not found, try whit another key") + env.fout(licenses.cancel_item(key, immediate)) diff --git a/SoftLayer/CLI/licenses/create.py b/SoftLayer/CLI/licenses/create.py index d3d779c96..83c7c5a0d 100644 --- a/SoftLayer/CLI/licenses/create.py +++ b/SoftLayer/CLI/licenses/create.py @@ -2,28 +2,29 @@ # :licenses: MIT, see LICENSE for more details. import click -from SoftLayer.managers import ordering + +import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting @click.command() -@click.option('--key', '-k', required=True, prompt=True, help="The License Key for this specific Account License.") +@click.option('--key', '-k', required=True, prompt=True, + help="The VMware License Key. " + "To get could use the product_package::getItems id=301 with name Software License Package" + "E.g VMWARE_VSAN_ENTERPRISE_TIER_III_65_124_TB_6_X_2") @click.option('--datacenter', '-d', required=True, prompt=True, help="Datacenter shortname") @environment.pass_env def cli(env, key, datacenter): - """Order/create a Vm licenses instance.""" + """Order/Create License.""" - complex_type = 'SoftLayer_Container_Product_Order_Software_License' item_package = [key] - ordering_manager = ordering.OrderingManager(env.client) - result = ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', - location=datacenter, - item_keynames=item_package, - complex_type=complex_type, - hourly=False) + licenses = SoftLayer.LicensesManager(env.client) + + result = licenses.create(datacenter, item_package) + table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 8053ec70e..1b2ddf6f9 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -17,6 +17,7 @@ from SoftLayer.managers.hardware import HardwareManager from SoftLayer.managers.image import ImageManager from SoftLayer.managers.ipsec import IPSECManager +from SoftLayer.managers.license import LicensesManager from SoftLayer.managers.load_balancer import LoadBalancerManager from SoftLayer.managers.metadata import MetadataManager from SoftLayer.managers.network import NetworkManager @@ -43,6 +44,7 @@ 'HardwareManager', 'ImageManager', 'IPSECManager', + 'LicensesManager', 'LoadBalancerManager', 'MetadataManager', 'NetworkManager', diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index 84e5e538f..c529e7e67 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -6,18 +6,20 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +from SoftLayer.CLI import exceptions +from SoftLayer.managers import ordering +from SoftLayer import utils class LicensesManager(object): - """Manages account lincese.""" + """Manages account license.""" def __init__(self, client): self.client = client def get_all_objects(self): - """Show the all VM ware licenses of account. + """Show the all VMware licenses of an account. """ _mask = '''softwareDescription,billingItem''' @@ -25,16 +27,35 @@ def get_all_objects(self): return self.client.call('SoftLayer_Software_AccountLicense', 'getAllObjects', mask=_mask) - def cancel_item(self, identifier, cancel_immediately, - reason_cancel, customer_note): + def cancel_item(self, key, cancel_immediately=False): """Cancel a billing item immediately, deleting all its data. :param integer identifier: the instance ID to cancel :param string reason_cancel: reason cancel """ - return self.client.call('SoftLayer_Billing_Item', 'cancelItem', - cancel_immediately, - True, - reason_cancel, - customer_note, - id=identifier) + vm_ware_licenses = self.get_all_objects() + vm_ware_find = False + for vm_ware in vm_ware_licenses: + if vm_ware.get('key') == key: + vm_ware_find = True + self.client.call('SoftLayer_Billing_Item', 'cancelItem', + cancel_immediately, + True, + 'Cancel by cli command', + 'Cancel by cli command', + id=utils.lookup(vm_ware, 'billingItem', 'id')) + + if not vm_ware_find: + raise exceptions.CLIAbort( + "Unable to find license key: {}".format(key)) + return vm_ware_find + + def create(self, datacenter, item_package): + + complex_type = 'SoftLayer_Container_Product_Order_Software_License' + ordering_manager = ordering.OrderingManager(self.client) + return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', + location=datacenter, + item_keynames=item_package, + complex_type=complex_type, + hourly=False) From 22f58ce2bb7271dfe3d21bbecdb2050b08e7de30 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 24 Jun 2021 18:10:05 -0400 Subject: [PATCH 0699/1582] fix the team code review comments --- SoftLayer/managers/license.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SoftLayer/managers/license.py b/SoftLayer/managers/license.py index c529e7e67..00b854c16 100644 --- a/SoftLayer/managers/license.py +++ b/SoftLayer/managers/license.py @@ -51,7 +51,11 @@ def cancel_item(self, key, cancel_immediately=False): return vm_ware_find def create(self, datacenter, item_package): + """Create a license + :param string datacenter: the datacenter shortname + :param string[] item_package: items array + """ complex_type = 'SoftLayer_Container_Product_Order_Software_License' ordering_manager = ordering.OrderingManager(self.client) return ordering_manager.place_order(package_keyname='SOFTWARE_LICENSE_PACKAGE', From f3d271668ee6288570ac8fb933798af2884e6ae7 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 25 Jun 2021 18:16:19 -0400 Subject: [PATCH 0700/1582] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 17 +++++++++++++---- SoftLayer/fixtures/SoftLayer_Network_Pod.py | 10 ++++++++++ SoftLayer/managers/network.py | 7 +++++++ tests/CLI/modules/vlan_tests.py | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index ad45735e7..1b95ae8b7 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -1,10 +1,11 @@ """Order/create a VLAN instance.""" # :license: MIT, see LICENSE for more details. - import click +import SoftLayer from SoftLayer.managers import ordering from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions from SoftLayer.CLI import formatting @@ -22,10 +23,18 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' - + extras = {'name': name} if pod and not datacenter: datacenter = pod.split('.')[0] - + mgr = SoftLayer.NetworkManager(env.client) + pods = mgr.get_router() + for router in pods: + if router.get('name') == pod: + extras['routerId'] = router.get('frontendRouterId') + break + if not extras.get('routerId'): + raise exceptions.CLIAbort( + "Unable to find pod name: {}".format(pod)) if not network: item_package = ['PRIVATE_NETWORK_VLAN'] @@ -35,7 +44,7 @@ def cli(env, name, datacenter, pod, network, billing): item_keynames=item_package, complex_type=complex_type, hourly=billing, - extras={'name': name}) + extras=extras) table = formatting.KeyValueTable(['name', 'value']) table.align['name'] = 'r' table.align['value'] = 'l' diff --git a/SoftLayer/fixtures/SoftLayer_Network_Pod.py b/SoftLayer/fixtures/SoftLayer_Network_Pod.py index 4e6088270..0a96521ba 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_Pod.py +++ b/SoftLayer/fixtures/SoftLayer_Network_Pod.py @@ -18,5 +18,15 @@ 'frontendRouterId': 1114993, 'frontendRouterName': 'fcr01a.wdc07', 'name': 'wdc07.pod01' + }, + { + 'backendRouterId': 1234567, + 'backendRouterName': 'bcr01a.wdc07', + 'datacenterId': 2017603, + 'datacenterLongName': 'Washington 7', + 'datacenterName': 'wdc07', + 'frontendRouterId': 258741369, + 'frontendRouterName': 'fcr01a.wdc07', + 'name': 'TEST00.pod2' } ] diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index 7c86dda58..d57c8050c 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -774,3 +774,10 @@ def cancel_item(self, identifier, cancel_immediately, reason_cancel, customer_note, id=identifier) + + def get_router(self): + """return routers account. + + Returns routers. + """ + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 86884dff6..e0efe0271 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -130,7 +130,7 @@ def test_create_vlan_pod(self): result = self.run_command(['vlan', 'create', '--name', 'test', - '-p TEST00.pod2', + '-p', 'TEST00.pod2', '--network', 'public', '--billing', 'hourly' ]) From 835d1ee9c5b225f7b1d0e696ccb1c5b7e7a35c1b Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 28 Jun 2021 12:30:17 -0400 Subject: [PATCH 0701/1582] fix the team code review comments --- SoftLayer/CLI/licenses/create_options.py | 7 ++++--- SoftLayer/managers/licenses.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/SoftLayer/CLI/licenses/create_options.py b/SoftLayer/CLI/licenses/create_options.py index d4f027f3d..80e8268a2 100644 --- a/SoftLayer/CLI/licenses/create_options.py +++ b/SoftLayer/CLI/licenses/create_options.py @@ -5,7 +5,7 @@ from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer.managers import licenses +from SoftLayer.managers.licenses import LicensesManager from SoftLayer import utils @@ -14,15 +14,16 @@ def cli(env): """Server order options for a given chassis.""" - licenses_manager = licenses.LicensesManager(env.client) + licenses_manager = LicensesManager(env.client) options = licenses_manager.get_create_options() - table = formatting.Table(['Id', 'description', 'keyName', 'recurringFee']) + table = formatting.Table(['Id', 'description', 'keyName', 'capacity', 'recurringFee']) for item in options: table.add_row([item.get('id'), utils.trim_to(item.get('description'), 40), item.get('keyName'), + item.get('capacity'), item.get('prices')[0]['recurringFee']]) env.fout(table) diff --git a/SoftLayer/managers/licenses.py b/SoftLayer/managers/licenses.py index 3d6525382..3f2dbb7c4 100644 --- a/SoftLayer/managers/licenses.py +++ b/SoftLayer/managers/licenses.py @@ -5,9 +5,10 @@ :license: MIT, see LICENSE for more details. """ - # pylint: disable=too-many-public-methods +LICENSE_PACKAGE_ID = 301 + class LicensesManager(object): """Manages account lincese.""" @@ -18,8 +19,7 @@ def __init__(self, client): def get_create_options(self): """Returns valid options for ordering Licenses. - :param string datacenter: short name, like dal09 """ return self.client.call('SoftLayer_Product_Package', 'getItems', - id=301) + id=LICENSE_PACKAGE_ID) From 366dc47256a88b7cbcade41f04d5f2c3f36332a0 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 30 Jun 2021 18:29:43 -0400 Subject: [PATCH 0702/1582] Fix the Christopher code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++--- SoftLayer/managers/network.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 1b95ae8b7..0927daf99 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -24,10 +24,10 @@ def cli(env, name, datacenter, pod, network, billing): item_package = ['PUBLIC_NETWORK_VLAN'] complex_type = 'SoftLayer_Container_Product_Order_Network_Vlan' extras = {'name': name} - if pod and not datacenter: + if pod: datacenter = pod.split('.')[0] mgr = SoftLayer.NetworkManager(env.client) - pods = mgr.get_router() + pods = mgr.get_pods() for router in pods: if router.get('name') == pod: extras['routerId'] = router.get('frontendRouterId') @@ -35,7 +35,7 @@ def cli(env, name, datacenter, pod, network, billing): if not extras.get('routerId'): raise exceptions.CLIAbort( "Unable to find pod name: {}".format(pod)) - if not network: + if network == 'private': item_package = ['PRIVATE_NETWORK_VLAN'] ordering_manager = ordering.OrderingManager(env.client) diff --git a/SoftLayer/managers/network.py b/SoftLayer/managers/network.py index d57c8050c..638ce4f91 100644 --- a/SoftLayer/managers/network.py +++ b/SoftLayer/managers/network.py @@ -775,9 +775,13 @@ def cancel_item(self, identifier, cancel_immediately, customer_note, id=identifier) - def get_router(self): - """return routers account. + def get_pods(self, datacenter=None): + """Calls SoftLayer_Network_Pod::getAllObjects() - Returns routers. + returns list of all network pods and their routers. """ - return self.client.call('SoftLayer_Network_Pod', 'getAllObjects') + _filter = None + if datacenter: + _filter = {"datacenterName": {"operation": datacenter}} + + return self.client.call('SoftLayer_Network_Pod', 'getAllObjects', filter=_filter) From 4af46bdde1e0d67752b0d85aaa4beee45aa9707a Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 1 Jul 2021 17:06:52 -0400 Subject: [PATCH 0703/1582] Fix analysis issues. --- SoftLayer/CLI/block/count.py | 4 ++-- SoftLayer/CLI/core.py | 6 +++--- SoftLayer/CLI/file/count.py | 4 ++-- SoftLayer/CLI/formatting.py | 8 ++++---- SoftLayer/CLI/loadbal/detail.py | 4 ++-- SoftLayer/CLI/loadbal/pools.py | 6 +++--- SoftLayer/CLI/ssl/add.py | 20 ++++++++++++-------- SoftLayer/CLI/ssl/edit.py | 20 ++++++++++++-------- SoftLayer/CLI/template.py | 10 +++++----- SoftLayer/CLI/virt/bandwidth.py | 8 ++++---- SoftLayer/utils.py | 2 +- 11 files changed, 50 insertions(+), 42 deletions(-) diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index dc4fb89c1..9f5fd35cf 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -37,6 +37,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 7257c59d9..1dcd68acb 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -54,17 +54,17 @@ def list_commands(self, ctx): return sorted(env.list_commands(*self.path)) - def get_command(self, ctx, name): + def get_command(self, ctx, cmd_name): """Get command for click.""" env = ctx.ensure_object(environment.Environment) env.load() # Do alias lookup (only available for root commands) if len(self.path) == 0: - name = env.resolve_alias(name) + cmd_name = env.resolve_alias(cmd_name) new_path = list(self.path) - new_path.append(name) + new_path.append(cmd_name) module = env.get_command(*new_path) if isinstance(module, types.ModuleType): return CommandLoader(*new_path, help=module.__doc__ or '') diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index addb14300..215d5c4d7 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -36,6 +36,6 @@ def cli(env, sortby, datacenter): table = formatting.KeyValueTable(DEFAULT_COLUMNS) table.sortby = sortby - for datacenter_name in datacenters: - table.add_row([datacenter_name, datacenters[datacenter_name]]) + for key, value in datacenters.items(): + table.add_row([key, value]) env.fout(table) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 0462197c0..b28c54fe6 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -248,11 +248,11 @@ def __str__(self): class CLIJSONEncoder(json.JSONEncoder): """A JSON encoder which is able to use a .to_python() method on objects.""" - def default(self, obj): + def default(self, o): """Encode object if it implements to_python().""" - if hasattr(obj, 'to_python'): - return obj.to_python() - return super().default(obj) + if hasattr(o, 'to_python'): + return o.to_python() + return super().default(o) class Table(object): diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index eb832d594..35e10f376 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -83,8 +83,8 @@ def lbaas_table(this_lb): # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] - for uuid in pools: - member_col.append(pools[uuid]) + for uuid in pools.values(): + member_col.append(uuid) member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ diff --git a/SoftLayer/CLI/loadbal/pools.py b/SoftLayer/CLI/loadbal/pools.py index bfd1d48f7..bf77a4c03 100644 --- a/SoftLayer/CLI/loadbal/pools.py +++ b/SoftLayer/CLI/loadbal/pools.py @@ -114,9 +114,9 @@ def edit(env, identifier, listener, **args): 'sslcert': 'tlsCertificateId' } - for arg in args: - if args[arg]: - new_listener[arg_to_option[arg]] = args[arg] + for key, value in args.items(): + if value: + new_listener[arg_to_option[key]] = value try: mgr.add_lb_listener(uuid, new_listener) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index c017cb5b3..161b48c5e 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,15 +28,19 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - template['certificate'] = open(crt).read() - template['privateKey'] = open(key).read() - if csr: - body = open(csr).read() - template['certificateSigningRequest'] = body + with open(crt) as file_crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + body = file_csr.read() + template['certificateSigningRequest'] = body - if icc: - body = open(icc).read() - template['intermediateCertificate'] = body + with open(icc) as file_icc: + if icc: + body = file_icc.read() + template['intermediateCertificate'] = body manager = SoftLayer.SSLManager(env.client) manager.add_certificate(template) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index 4893ebf8f..da899f34f 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,14 +24,18 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - if crt: - template['certificate'] = open(crt).read() - if key: - template['privateKey'] = open(key).read() - if csr: - template['certificateSigningRequest'] = open(csr).read() - if icc: - template['intermediateCertificate'] = open(icc).read() + with open(crt) as file_crt: + if crt: + template['certificate'] = file_crt.read() + with open(key) as file_key: + if key: + template['privateKey'] = file_key.read() + with open(csr) as file_csr: + if csr: + template['certificateSigningRequest'] = file_csr.read() + with open(icc) as file_icc: + if icc: + template['intermediateCertificate'] = file_icc.read() if notes: template['notes'] = notes diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 437978f78..012644aa6 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,11 +24,11 @@ def __call__(self, ctx, param, value): if value is None: return - config = configparser.ConfigParser() - ini_str = '[settings]\n' + open( - os.path.expanduser(value), 'r').read() - ini_fp = io.StringIO(ini_str) - config.read_file(ini_fp) + with open(os.path.expanduser(value), 'r') as file_handle: + config = configparser.ConfigParser() + ini_str = '[settings]\n' + file_handle.read() + ini_fp = io.StringIO(ini_str) + config.read_file(ini_fp) # Merge template options with the options passed in args = {} diff --git a/SoftLayer/CLI/virt/bandwidth.py b/SoftLayer/CLI/virt/bandwidth.py index 2f29cc7f8..68d3d986c 100644 --- a/SoftLayer/CLI/virt/bandwidth.py +++ b/SoftLayer/CLI/virt/bandwidth.py @@ -71,15 +71,15 @@ def create_bandwidth_table(data, summary_period, title="Bandwidth Report"): {'keyName': 'privateOut_net_octet', 'sum': 0.0, 'max': 0, 'name': 'Pri Out'}, ] - for point in formatted_data: - new_row = [point] + for key, value in formatted_data.items(): + new_row = [key] for bw_type in bw_totals: - counter = formatted_data[point].get(bw_type['keyName'], 0) + counter = value.get(bw_type['keyName'], 0) new_row.append(mb_to_gb(counter)) bw_type['sum'] = bw_type['sum'] + counter if counter > bw_type['max']: bw_type['max'] = counter - bw_type['maxDate'] = point + bw_type['maxDate'] = key table.add_row(new_row) for bw_type in bw_totals: diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 5bac80696..e38760861 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=')[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 03539d86156be95253a245f189e937d94512e721 Mon Sep 17 00:00:00 2001 From: caberos Date: Fri, 2 Jul 2021 10:24:48 -0400 Subject: [PATCH 0704/1582] fix the toox tool and update --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index e38760861..0641b019c 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -228,7 +228,7 @@ def build_filter_orderby(orderby): for keyword in reverse_filter: _aux_filter = {} if '=' in keyword: - _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) + _aux_filter[str(keyword).split('=', maxsplit=1)[0]] = query_filter_orderby(str(keyword).split('=')[1]) _filters = _aux_filter elif keyword == list(reverse_filter)[0]: _aux_filter[keyword] = query_filter_orderby('DESC') From 13fb0879752bfff2f4dedd342684a8aa20b706f0 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 5 Jul 2021 16:41:55 -0500 Subject: [PATCH 0705/1582] 5.9.6 change log and updates --- CHANGELOG.md | 23 +++++++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a79b6d82..7736e005f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Change Log +## [5.9.6] - 2021-07-05 +https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 + +#### Improvements +- Updated snap to core20 and edited README #1494 +- Add a table result for `slcli hw upgrade` output. #1488 +- Remove block/file interval option for replica volume. #1497 +- `slcli vlan cancel` should report if a vlan is automatic. #1495 +- New method to manage how long text is in output tables. #1506 +- Fix Tox-analysis issues. #1510 + +#### New Commands +- add new email feature #1483 + + `slcli email list` + + `slcli email detail` + + `slcli email edit` +- `slcli vlan cancel` +- Add slcli account licenses #1501 + + `slcli account licenses` +- Create a new commands on slcli that create/cancel a VMware licenses #1504 + + `slcli licenses create` + + `slcli licenses cancel` + ## [5.9.5] - 2021-05-25 https://github.com/softlayer/softlayer-python/compare/v5.9.4...v5.9.5 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 26a00c4cd..5eddbd2b4 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.5' +VERSION = 'v5.9.6' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index c2e70f61a..43bacbb48 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.5', + version='5.9.6', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 3835c9a848c9582cb300fd732117e505c8e5c14b Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 11:25:41 -0400 Subject: [PATCH 0706/1582] fix the team code review comments --- SoftLayer/CLI/vlan/create.py | 6 +++++- tests/managers/network_tests.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/vlan/create.py b/SoftLayer/CLI/vlan/create.py index 0927daf99..a3b88f85b 100644 --- a/SoftLayer/CLI/vlan/create.py +++ b/SoftLayer/CLI/vlan/create.py @@ -30,7 +30,10 @@ def cli(env, name, datacenter, pod, network, billing): pods = mgr.get_pods() for router in pods: if router.get('name') == pod: - extras['routerId'] = router.get('frontendRouterId') + if network == 'public': + extras['routerId'] = router.get('frontendRouterId') + elif network == 'private': + extras['routerId'] = router.get('backendRouterId') break if not extras.get('routerId'): raise exceptions.CLIAbort( @@ -50,5 +53,6 @@ def cli(env, name, datacenter, pod, network, billing): table.align['value'] = 'l' table.add_row(['id', result['orderId']]) table.add_row(['created', result['orderDate']]) + table.add_row(['name', result['orderDetails']['orderContainers'][0]['name']]) env.fout(table) diff --git a/tests/managers/network_tests.py b/tests/managers/network_tests.py index 2463ed999..a578fd604 100644 --- a/tests/managers/network_tests.py +++ b/tests/managers/network_tests.py @@ -624,3 +624,7 @@ def test_vlan_edit(self): self.network.edit(vlan_id, name, note, tags) self.assert_called_with('SoftLayer_Network_Vlan', 'editObject') + + def test_get_all_pods(self): + self.network.get_pods() + self.assert_called_with('SoftLayer_Network_Pod', 'getAllObjects') From 6112c5419dc81b356115f5c51346cb7f5d33d6d6 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 6 Jul 2021 12:20:35 -0400 Subject: [PATCH 0707/1582] fix the unit test and tox tool --- SoftLayer/fixtures/SoftLayer_Product_Order.py | 3 +++ tests/CLI/modules/vlan_tests.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Product_Order.py b/SoftLayer/fixtures/SoftLayer_Product_Order.py index e420315e3..b0d67d868 100644 --- a/SoftLayer/fixtures/SoftLayer_Product_Order.py +++ b/SoftLayer/fixtures/SoftLayer_Product_Order.py @@ -287,6 +287,9 @@ vlan_placeOrder = {"orderDate": "2021-06-02 15:23:47", "orderId": 123456, + "orderDetails": { + "orderContainers": [{ + "name": "test"}]}, "prices": [{ "id": 2018, "itemId": 1071, diff --git a/tests/CLI/modules/vlan_tests.py b/tests/CLI/modules/vlan_tests.py index 1397f08b0..204788d4d 100644 --- a/tests/CLI/modules/vlan_tests.py +++ b/tests/CLI/modules/vlan_tests.py @@ -155,7 +155,7 @@ def test_create_vlan(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) def test_create_vlan_pod(self): _mock = self.set_mock('SoftLayer_Product_Package', 'getItems') @@ -173,7 +173,7 @@ def test_create_vlan_pod(self): self.assert_no_fail(result) self.assertEqual(json.loads(result.output), - {'id': 123456, 'created': '2021-06-02 15:23:47'}) + {'id': 123456, 'created': '2021-06-02 15:23:47', 'name': 'test'}) @mock.patch('SoftLayer.CLI.formatting.no_going_back') def test_vlan_cancel(self, confirm_mock): From adee15c8e3e9ad07321bd075bb321947a00d221a Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:16:53 -0500 Subject: [PATCH 0708/1582] Fixed some doc block issues when generating HTML --- SoftLayer/API.py | 1 + SoftLayer/managers/hardware.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index a32491ba7..21f21ffc6 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -31,6 +31,7 @@ 'BaseClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT', + 'IAMClient', ] VALID_CALL_ARGS = set(( diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index d7e65694e..0f410b322 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -901,7 +901,7 @@ def get_maintenance_windows_detail(self, location_id): """Get the disks prices to be added or upgraded. :param int location_id: Hardware Server location id. - :return int. + :return int: """ result = None begin_date_object = datetime.datetime.now() From 6017a35746ef1286205d2dcd09838b9b342e09e4 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:23 -0500 Subject: [PATCH 0709/1582] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f3b39abf9..f33420f68 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ test-pypi ] + branches: [ master ] jobs: build-n-publish: @@ -12,10 +12,10 @@ jobs: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@master - - name: Set up Python 3.7 + - name: Set up Python 3.8 uses: actions/setup-python@v1 with: - python-version: 3.7 + python-version: 3.8 - name: Install pypa/build run: >- python -m From 4d755f08ee22e498b32f4d2bece49e079e5af108 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 15:33:38 -0500 Subject: [PATCH 0710/1582] updating test-pypi release action --- .github/workflows/test_pypi_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index f33420f68..439ed17cb 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,7 +4,7 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master ] + branches: [ master, test-pypi ] jobs: build-n-publish: From 198937ae1e0cf7f85a73158d0c35c635fe1e70f5 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 6 Jul 2021 16:12:28 -0500 Subject: [PATCH 0711/1582] updated release workflow to publish to test pypi --- .github/workflows/release.yml | 30 ++++++++++++++++++++++++- .github/workflows/test_pypi_release.yml | 6 ++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b244a86b..f4fd12536 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Release +name: Release Snapcraft and PyPi (Testing) on: release: @@ -20,4 +20,32 @@ jobs: VERSION=`snapcraft list-revisions slcli --arch ${{ matrix.arch }} | grep "edge\*" | awk '{print $1}'` echo Publishing $VERSION on ${{ matrix.arch }} snapcraft release slcli $VERSION stable + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + . + - name: Publish 📦 to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.CGALLO_TEST_PYPI }} + repository_url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test_pypi_release.yml b/.github/workflows/test_pypi_release.yml index 439ed17cb..aea906c54 100644 --- a/.github/workflows/test_pypi_release.yml +++ b/.github/workflows/test_pypi_release.yml @@ -4,16 +4,16 @@ name: Publish 📦 to TestPyPI on: push: - branches: [ master, test-pypi ] + branches: [test-pypi ] jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Set up Python 3.8 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install pypa/build From f6af86dafebc2b8f041248c96a0278f7b24d0ad8 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:28:42 -0500 Subject: [PATCH 0712/1582] Adding in CodeQL Analysis --- .github/workflows/codeql-analysis.yml | 71 +++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..19d4bd814 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '41 6 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 1162f3fe9d07071463443331ee38695dbf50f210 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 7 Jul 2021 15:46:40 -0500 Subject: [PATCH 0713/1582] Create SECURITY.md --- SECURITY.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..290f09332 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +Generally only the latest release will be actively worked on and supported. +Version 5.7.2 is the last version that supports python2.7. + +| Version | Supported | +| ------- | ------------------ | +| 5.9.x | :white_check_mark: | +| 5.7.2 | :white_check_mark: | +| < 5.7.2 | :x: | + +## Reporting a Vulnerability + +Create a new [Bug Report](https://github.com/softlayer/softlayer-python/issues/new?assignees=&labels=Bug&template=bug_report.md&title=) to let us know about any vulnerabilities in the code base. From 72f8eb19c8f2affe3aa3a1a35b29448b197530a2 Mon Sep 17 00:00:00 2001 From: Fernando Date: Thu, 8 Jul 2021 11:14:15 -0400 Subject: [PATCH 0714/1582] Refactor the cdn edit option. --- SoftLayer/CLI/cdn/edit.py | 4 ++-- SoftLayer/managers/cdn.py | 3 ++- tests/CLI/modules/cdn_tests.py | 16 ++++++---------- tests/managers/cdn_tests.py | 3 +-- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/SoftLayer/CLI/cdn/edit.py b/SoftLayer/CLI/cdn/edit.py index 677bcd4d7..f39e20a02 100644 --- a/SoftLayer/CLI/cdn/edit.py +++ b/SoftLayer/CLI/cdn/edit.py @@ -20,7 +20,6 @@ help="HTTP port." ) @click.option('--origin', '-o', - required=True, type=click.STRING, help="Origin server address." ) @@ -43,7 +42,7 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, performance_configuration): """Edit a CDN Account. - You can use the hostname or uniqueId as IDENTIFIER. + Note: You can use the hostname or uniqueId as IDENTIFIER. """ manager = SoftLayer.CDNManager(env.client) @@ -77,5 +76,6 @@ def cli(env, identifier, header, http_port, origin, respect_headers, cache, perf table.add_row(['Vendor Name', cdn.get('vendorName')]) table.add_row(['Cache key optimization', cdn.get('cacheKeyQueryRule')]) table.add_row(['cname', cdn.get('cname')]) + table.add_row(['Origin server address', cdn.get('originHost')]) env.fout(table) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7189cbaa4..7edb97628 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -199,7 +199,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'vendorName': cdn_instance_detail.get('vendorName'), 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), - 'httpPort': cdn_instance_detail.get('httpPort') + 'httpPort': cdn_instance_detail.get('httpPort'), + 'origin': cdn_instance_detail.get('originHost') } if header: diff --git a/tests/CLI/modules/cdn_tests.py b/tests/CLI/modules/cdn_tests.py index 5bff7e647..c0a96fee4 100644 --- a/tests/CLI/modules/cdn_tests.py +++ b/tests/CLI/modules/cdn_tests.py @@ -97,36 +97,32 @@ def test_remove_origin(self): self.assertEqual(result.output, "Origin with path /example1 has been deleted\n") def test_edit_header(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--header=www.test.com']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--header=www.test.com']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('www.test.com', header_result['Header']) def test_edit_http_port(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--http-port=83']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--http-port=83']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(83, header_result['Http Port']) def test_edit_respect_headers(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--respect-headers=1']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--respect-headers=1']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual(True, header_result['Respect Headers']) def test_edit_cache(self): - result = self.run_command(['cdn', 'edit', 'test.example.com', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', 'test.example.com', '--cache', 'include-specified', + '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) def test_edit_cache_by_uniqueId(self): - result = self.run_command(['cdn', 'edit', '9934111111111', - '--origin=10.34.12.125', '--cache', 'include-specified', '--cache', 'test']) + result = self.run_command(['cdn', 'edit', '9934111111111', '--cache', 'include-specified', '--cache', 'test']) self.assert_no_fail(result) header_result = json.loads(result.output) self.assertEqual('include: test', header_result['Cache key optimization']) diff --git a/tests/managers/cdn_tests.py b/tests/managers/cdn_tests.py index a57fe0b68..7e56f81ab 100644 --- a/tests/managers/cdn_tests.py +++ b/tests/managers/cdn_tests.py @@ -107,8 +107,7 @@ def test_purge_content(self): def test_cdn_edit(self): identifier = '9934111111111' header = 'www.test.com' - origin = '1.1.1.1' - result = self.cdn_client.edit(identifier, header=header, origin=origin) + result = self.cdn_client.edit(identifier, header=header) self.assertEqual(fixtures.SoftLayer_Network_CdnMarketplace_Configuration_Mapping. updateDomainMapping, result) From 835c5af88eec9191af5845a6615d3e55e6646ea1 Mon Sep 17 00:00:00 2001 From: Fernando Date: Mon, 12 Jul 2021 19:12:20 -0400 Subject: [PATCH 0715/1582] Refactor the cdn edit option. --- SoftLayer/managers/cdn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 7edb97628..daa7ff377 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -200,7 +200,8 @@ def edit(self, identifier, header=None, http_port=None, origin=None, 'cname': cdn_instance_detail.get('cname'), 'domain': cdn_instance_detail.get('domain'), 'httpPort': cdn_instance_detail.get('httpPort'), - 'origin': cdn_instance_detail.get('originHost') + 'origin': cdn_instance_detail.get('originHost'), + 'header': cdn_instance_detail.get('header') } if header: From 9a2752b96d6cd707f1cac31d90a6f06782721fe6 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 26 Jul 2021 17:52:33 -0400 Subject: [PATCH 0716/1582] Refactor loadbal order-options --- SoftLayer/CLI/loadbal/order.py | 103 ++++++++++++++++------------- tests/CLI/modules/loadbal_tests.py | 8 +-- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 2293da8db..e36b28bc9 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -89,53 +89,64 @@ def order_options(env, datacenter): net_mgr = SoftLayer.NetworkManager(env.client) package = mgr.lbaas_order_options() - for region in package['regions']: - dc_name = utils.lookup(region, 'location', 'location', 'name') - - # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: - continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) - - l_groups = [] - for group in region['location']['location']['groups']: - l_groups.append(group.get('id')) - - # Price lookups - prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) - for item in package['items']: - i_price = {'keyName': item['keyName']} - for price in item.get('prices', []): - if not price.get('locationGroupId'): - i_price['default_price'] = price.get('hourlyRecurringFee') - elif price.get('locationGroupId') in l_groups: - i_price['region_price'] = price.get('hourlyRecurringFee') - prices.append(i_price) - for price in prices: - if price.get('region_price'): - price_table.add_row([price.get('keyName'), price.get('region_price')]) - else: - price_table.add_row([price.get('keyName'), price.get('default_price')]) - - # Vlan/Subnet Lookups - mask = "mask[networkVlan,podName,addressSpace]" - subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) - - for subnet in subnets: - # Only show these types, easier to filter here than in an API call. - if subnet.get('subnetType') != 'PRIMARY' and subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + if not datacenter: + data_table = formatting.KeyValueTable(['Datacenters', 'City']) + for region in package['regions']: + data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) + # print(region) + env.fout(data_table) + click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + "to find pricing information and private subnets for that specific site.") + + else: + for region in package['regions']: + dc_name = utils.lookup(region, 'location', 'location', 'name') + + # Skip locations if they are not the one requested. + if datacenter and dc_name != datacenter: continue - space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) - vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) - subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - - env.fout(this_table) + this_table = formatting.Table( + ['Prices', 'Private Subnets'], + title="{}: {}".format(region['keyname'], region['description']) + ) + + l_groups = [] + for group in region['location']['location']['groups']: + l_groups.append(group.get('id')) + + # Price lookups + prices = [] + price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + for item in package['items']: + i_price = {'keyName': item['keyName']} + for price in item.get('prices', []): + if not price.get('locationGroupId'): + i_price['default_price'] = price.get('hourlyRecurringFee') + elif price.get('locationGroupId') in l_groups: + i_price['region_price'] = price.get('hourlyRecurringFee') + prices.append(i_price) + for price in prices: + if price.get('region_price'): + price_table.add_row([price.get('keyName'), price.get('region_price')]) + else: + price_table.add_row([price.get('keyName'), price.get('default_price')]) + + # Vlan/Subnet Lookups + mask = "mask[networkVlan,podName,addressSpace]" + subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + + for subnet in subnets: + # Only show these types, easier to filter here than in an API call. + if subnet.get('subnetType') != 'PRIMARY' and \ + subnet.get('subnetType') != 'ADDITIONAL_PRIMARY': + continue + space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) + vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) + subnet_table.add_row([subnet.get('id'), space, vlan]) + this_table.add_row([price_table, subnet_table]) + + env.fout(this_table) @click.command() diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8576a8292..5a9a91134 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -258,11 +258,11 @@ def test_verify_order(self): self.assert_no_fail(result) def test_order_options(self): - mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') - mock.return_value = SoftLayer_Product_Package.getAllObjectsLoadbal + fault_string = 'Use `slcli lb order-options --datacenter `' \ + ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - - self.assert_no_fail(result) + self.assertIn("ERROR: {}".format(fault_string), + result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From 98f4a40541dce485280e8389b7434df50e521009 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 27 Jul 2021 16:33:57 -0400 Subject: [PATCH 0717/1582] fix the network space is empty on subnet detail --- SoftLayer/CLI/subnet/detail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/subnet/detail.py b/SoftLayer/CLI/subnet/detail.py index 7eb934431..e4ddc2f9e 100644 --- a/SoftLayer/CLI/subnet/detail.py +++ b/SoftLayer/CLI/subnet/detail.py @@ -26,7 +26,7 @@ def cli(env, identifier, no_vs, no_hardware): subnet_id = helpers.resolve_id(mgr.resolve_subnet_ids, identifier, name='subnet') - mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware]' + mask = 'mask[ipAddresses[id, ipAddress,note], datacenter, virtualGuests, hardware, networkVlan[networkSpace]]' subnet = mgr.get_subnet(subnet_id, mask=mask) From d4c55d2338156bbf929967db28e08cc8068c7e39 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 13:54:05 -0500 Subject: [PATCH 0718/1582] changed name of the snapcraft release build --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4fd12536..b009483c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,7 +5,7 @@ on: types: [published] jobs: - release: + snap-release: runs-on: ubuntu-18.04 strategy: matrix: From 19d98a686cc4c8169919ffeb0d7e79dd81b37fd3 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 15:55:38 -0400 Subject: [PATCH 0719/1582] slcli server create-options dal13 Error --- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 26b5dadff..5e91c997c 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -161,3 +161,8 @@ def test_get_active_account_licenses(self): def test_get_active_virtual_licenses(self): self.manager.get_active_virtual_licenses() self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") + + def test_get_routers_with_datacenter(self): + self.manager.get_routers('dal13') + object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file From d4591f058470b233c7e29fc4bcfb22198cead406 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 16:02:23 -0400 Subject: [PATCH 0720/1582] fix the tox analysis --- tests/managers/account_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 5e91c997c..24efd603e 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -165,4 +165,4 @@ def test_get_active_virtual_licenses(self): def test_get_routers_with_datacenter(self): self.manager.get_routers('dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} - self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) \ No newline at end of file + self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From 8c1677a917832ef98d207623aa7ec90ff3ba4e14 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Mon, 2 Aug 2021 16:03:22 -0500 Subject: [PATCH 0721/1582] prevents --version from looking at environment variables --- README.rst | 20 ++++++++++++++++++++ SoftLayer/CLI/core.py | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fc05a3503..7cf2e65b3 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,26 @@ InsecurePlatformWarning Notice ------------------------------ This library relies on the `requests `_ library to make HTTP requests. On Python versions below Python 2.7.9, requests has started emitting a security warning (InsecurePlatformWarning) due to insecurities with creating SSL connections. To resolve this, upgrade to Python 2.7.9+ or follow the instructions here: http://stackoverflow.com/a/29099439. +Basic Usage +----------- + +- `The Complete Command Directory `_ + +Advanced Usage +-------------- + +You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example + +.. code-block:: bash + $ export SLCLI_VERBOSE=3 + $ export SLCLI_FORMAT=json + $ slcli vs list + +is equivalent to + +.. code-block:: bash + $ slcli -vvv --format=json vs list + Getting Help ------------ Bugs and feature requests about this library should have a `GitHub issue `_ opened about them. diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 1dcd68acb..0f7c12f8c 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -129,7 +129,7 @@ def get_version_message(ctx, param, value): required=False, help="Use demo data instead of actually making API calls") @click.option('--version', is_flag=True, expose_value=False, is_eager=True, callback=get_version_message, - help="Show version information.") + help="Show version information.", allow_from_autoenv=False,) @environment.pass_env def cli(env, format='table', From b8e2a4b7b8691d52e912df4cabcae97192a03668 Mon Sep 17 00:00:00 2001 From: caberos Date: Mon, 2 Aug 2021 17:09:50 -0400 Subject: [PATCH 0722/1582] fix the team code review comments --- SoftLayer/CLI/hardware/create_options.py | 2 +- SoftLayer/managers/account.py | 2 +- tests/managers/account_tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/hardware/create_options.py b/SoftLayer/CLI/hardware/create_options.py index 7d104d877..646d37c09 100644 --- a/SoftLayer/CLI/hardware/create_options.py +++ b/SoftLayer/CLI/hardware/create_options.py @@ -21,7 +21,7 @@ def cli(env, prices, location=None): hardware_manager = hardware.HardwareManager(env.client) account_manager = account.AccountManager(env.client) options = hardware_manager.get_create_options(location) - routers = account_manager.get_routers(location) + routers = account_manager.get_routers(location=location) tables = [] # Datacenters diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..d9814b1ce 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, location=None, mask=None): + def get_routers(self, mask=None, location=None): """Gets all the routers currently active on the account :param string mask: Object Mask diff --git a/tests/managers/account_tests.py b/tests/managers/account_tests.py index 24efd603e..6d515de38 100644 --- a/tests/managers/account_tests.py +++ b/tests/managers/account_tests.py @@ -163,6 +163,6 @@ def test_get_active_virtual_licenses(self): self.assert_called_with("SoftLayer_Account", "getActiveVirtualLicenses") def test_get_routers_with_datacenter(self): - self.manager.get_routers('dal13') + self.manager.get_routers(location='dal13') object_filter = {'routers': {'topLevelLocation': {'name': {'operation': 'dal13'}}}} self.assert_called_with("SoftLayer_Account", "getRouters", filter=object_filter) From cbba4eb0f0932aa39e508e2be048f302f5c974b5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:12:42 -0400 Subject: [PATCH 0723/1582] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index e36b28bc9..704fd9704 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -95,7 +95,7 @@ def order_options(env, datacenter): data_table.add_row([region['description'].split('-')[0], region['description'].split('-')[1]]) # print(region) env.fout(data_table) - click.secho("ERROR: Use `slcli lb order-options --datacenter ` " + click.secho("Use `slcli lb order-options --datacenter ` " "to find pricing information and private subnets for that specific site.") else: @@ -105,10 +105,6 @@ def order_options(env, datacenter): # Skip locations if they are not the one requested. if datacenter and dc_name != datacenter: continue - this_table = formatting.Table( - ['Prices', 'Private Subnets'], - title="{}: {}".format(region['keyname'], region['description']) - ) l_groups = [] for group in region['location']['location']['groups']: @@ -116,7 +112,7 @@ def order_options(env, datacenter): # Price lookups prices = [] - price_table = formatting.KeyValueTable(['KeyName', 'Cost']) + price_table = formatting.KeyValueTable(['KeyName', 'Cost'], title='Prices') for item in package['items']: i_price = {'keyName': item['keyName']} for price in item.get('prices', []): @@ -134,7 +130,7 @@ def order_options(env, datacenter): # Vlan/Subnet Lookups mask = "mask[networkVlan,podName,addressSpace]" subnets = net_mgr.list_subnets(datacenter=dc_name, network_space='PRIVATE', mask=mask) - subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan']) + subnet_table = formatting.Table(['Id', 'Subnet', 'Vlan'], title='Private subnet') for subnet in subnets: # Only show these types, easier to filter here than in an API call. @@ -144,9 +140,9 @@ def order_options(env, datacenter): space = "{}/{}".format(subnet.get('networkIdentifier'), subnet.get('cidr')) vlan = "{}.{}".format(subnet['podName'], subnet['networkVlan']['vlanNumber']) subnet_table.add_row([subnet.get('id'), space, vlan]) - this_table.add_row([price_table, subnet_table]) - env.fout(this_table) + env.fout(price_table) + env.fout(subnet_table) @click.command() From b43d7342a8d11506a6d2288b106c7fd483c38ee5 Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 11:18:30 -0400 Subject: [PATCH 0724/1582] fix the unit test --- tests/CLI/modules/loadbal_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 5a9a91134..0fba53cea 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -261,8 +261,7 @@ def test_order_options(self): fault_string = 'Use `slcli lb order-options --datacenter `' \ ' to find pricing information and private subnets for that specific site.' result = self.run_command(['loadbal', 'order-options']) - self.assertIn("ERROR: {}".format(fault_string), - result.output) + self.assertIn(fault_string, result.output) def test_order_options_with_datacenter(self): mock = self.set_mock('SoftLayer_Product_Package', 'getAllObjects') From eee1c28877c0cfdc28553aa168c7552aa5e833ec Mon Sep 17 00:00:00 2001 From: caberos Date: Tue, 3 Aug 2021 18:43:45 -0400 Subject: [PATCH 0725/1582] fix the team code review comments --- SoftLayer/CLI/loadbal/order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/loadbal/order.py b/SoftLayer/CLI/loadbal/order.py index 704fd9704..9df0af1cc 100644 --- a/SoftLayer/CLI/loadbal/order.py +++ b/SoftLayer/CLI/loadbal/order.py @@ -103,7 +103,7 @@ def order_options(env, datacenter): dc_name = utils.lookup(region, 'location', 'location', 'name') # Skip locations if they are not the one requested. - if datacenter and dc_name != datacenter: + if datacenter and dc_name != datacenter.lower(): continue l_groups = [] From 2efe374cc7ea723787adf4d9ce99ff28636ae8f4 Mon Sep 17 00:00:00 2001 From: caberos Date: Wed, 4 Aug 2021 17:13:22 -0400 Subject: [PATCH 0726/1582] last fix team code review comments --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index d9814b1ce..e6337e716 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -277,7 +277,7 @@ def get_account_all_billing_orders(self, limit=100, mask=None): return self.client.call('Billing_Order', 'getAllObjects', limit=limit, mask=mask) - def get_routers(self, mask=None, location=None): + def get_routers(self, location=None, mask=None): """Gets all the routers currently active on the account :param string mask: Object Mask From 05288e9863a9bda034959f0b67ad2803f57daf0b Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:26:01 -0500 Subject: [PATCH 0727/1582] v5.9.7 changelog --- CHANGELOG.md | 18 ++++++++++++++++++ SoftLayer/consts.py | 2 +- setup.py | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7736e005f..cf0eb2848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## [5.9.7] - 2021-08-04 +https://github.com/softlayer/softlayer-python/compare/v5.9.6...v5.9.7 + +#### Improvements +- Fixed some doc block issues when generating HTML #1513 +- Updates to the Release workflow for publishing to test pypi #1514 + +- Adding in CodeQL Analysis #1517 +- Create SECURITY.md #1518 +- Fix the network space is empty on subnet detail #1523 +- Prevents SLCLI_VERSION environment variable from breaking things #1527 +- Refactor loadbal order-options #1521 +- slcli server create-options dal13 Error #1526 + +#### New Commands +- add new feature on vlan cli #1499 + + `slcli vlan create` + ## [5.9.6] - 2021-07-05 https://github.com/softlayer/softlayer-python/compare/v5.9.5...v5.9.6 diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 5eddbd2b4..6d55ed2df 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v5.9.6' +VERSION = 'v5.9.7' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3.1/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/' diff --git a/setup.py b/setup.py index 43bacbb48..4eec2cad5 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ setup( name='SoftLayer', - version='5.9.6', + version='5.9.7', description=DESCRIPTION, long_description=LONG_DESCRIPTION, author='SoftLayer, Inc., an IBM Company', From 7c6fb218811663c401b402abd4a3c62006f1ba74 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 4 Aug 2021 17:40:29 -0500 Subject: [PATCH 0728/1582] Fixed formatting in README --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 7cf2e65b3..15d5bcca3 100644 --- a/README.rst +++ b/README.rst @@ -87,6 +87,7 @@ Advanced Usage You can automatically set some parameters via environment variables with by using the SLCLI prefix. For example .. code-block:: bash + $ export SLCLI_VERBOSE=3 $ export SLCLI_FORMAT=json $ slcli vs list @@ -94,6 +95,7 @@ You can automatically set some parameters via environment variables with by usin is equivalent to .. code-block:: bash + $ slcli -vvv --format=json vs list Getting Help From 3dcfff51119f6e0358d1738a51c8e5c4b7180625 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 12 Aug 2021 18:27:41 -0400 Subject: [PATCH 0729/1582] #1530 fix code blocks formatting of The Solution section docs --- docs/api/client.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index f1692eb6a..c90aa3d88 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -228,11 +228,13 @@ The Solution Using the python dictionary's `.get() `_ is great for non-nested items. :: + print("{}, {}".format(item.get('id'), item.get('hostName'))) Otherwise, this SDK provides a util function to do something similar. Each additional argument passed into `utils.lookup` will go one level deeper into the nested dictionary to find the item requested, returning `None` if a KeyError shows up. :: + itemId = SoftLayer.utils.lookup(item, 'id') itemHostname = SoftLayer.utils.lookup(item, 'hostName') print("{}, {}".format(itemId, itemHostname)) From 499746eb972e0119f7407b67793327567fe5f92a Mon Sep 17 00:00:00 2001 From: ATGE Date: Wed, 18 Aug 2021 10:58:41 -0400 Subject: [PATCH 0730/1582] #1531 Add retry decorator to documentation --- docs/api/client.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index c90aa3d88..3f7700cbb 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -245,3 +245,11 @@ API Reference .. automodule:: SoftLayer :members: + :ignore-module-all: + +.. automodule:: SoftLayer.API + :members: + :ignore-module-all: + +.. automodule:: SoftLayer.decoration + :members: From 1d4a285260c9bc3126840e67534a8deec3a3e082 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:30:32 -0400 Subject: [PATCH 0731/1582] #1532 Add Utility reference to Documentation --- docs/api/client.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..563212577 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -253,3 +253,6 @@ API Reference .. automodule:: SoftLayer.decoration :members: + +.. automodule:: SoftLayer.utils + :members: From 8a62480a35284866a5848e31df38b7ee8d41de8b Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 16:33:57 -0400 Subject: [PATCH 0732/1582] #1532 Fix Sphinx WARNING: Inline emphasis start-string without end-string. --- SoftLayer/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 0641b019c..929b6524b 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -20,7 +20,7 @@ def lookup(dic, key, *keys): """A generic dictionary access helper. This helps simplify code that uses heavily nested dictionaries. It will - return None if any of the keys in *keys do not exist. + return None if any of the keys in `*keys` do not exist. :: From 5e594202114977c6715b301b244f149c55290d91 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 19 Aug 2021 19:05:01 -0400 Subject: [PATCH 0733/1582] #1533 Add Exceptions to Documentation --- docs/api/client.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api/client.rst b/docs/api/client.rst index 3f7700cbb..13b488bee 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -249,7 +249,10 @@ API Reference .. automodule:: SoftLayer.API :members: - :ignore-module-all: + :ignore-module-all: + +.. automodule:: SoftLayer.exceptions + :members: .. automodule:: SoftLayer.decoration :members: From 9ec4f324cfea39b8cc8f94ca9ecf3b79f21b2a10 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Tue, 31 Aug 2021 15:48:48 -0500 Subject: [PATCH 0734/1582] enforcing encodings on XMLRPC calls so we can safely use unicode characters --- SoftLayer/transports.py | 8 +++-- tests/transport_tests.py | 77 +++++++++++++++++++++++++++++++++------- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index bd643b568..e243f16f6 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -205,7 +205,8 @@ def __call__(self, request): request.url = '/'.join([self.endpoint_url, request.service]) request.payload = xmlrpc.client.dumps(tuple(largs), methodname=request.method, - allow_none=True) + allow_none=True, + encoding="iso-8859-1") # Prefer the request setting, if it's not None verify = request.verify @@ -214,7 +215,7 @@ def __call__(self, request): try: resp = self.client.request('POST', request.url, - data=request.payload, + data=request.payload.encode(), auth=auth, headers=request.transport_headers, timeout=self.timeout, @@ -273,7 +274,7 @@ def print_reproduceable(self, request): #auth=HTTPBasicAuth('apikey', YOUR_CLOUD_API_KEY) auth=None url = '$url' -payload = """$payload""" +payload = $payload transport_headers = $transport_headers timeout = $timeout verify = $verify @@ -287,6 +288,7 @@ def print_reproduceable(self, request): safe_payload = re.sub(r'[a-z0-9]{64}', r'API_KEY_GOES_HERE', request.payload) safe_payload = re.sub(r'(\s+)', r' ', safe_payload) + safe_payload = safe_payload.encode() substitutions = dict(url=request.url, payload=safe_payload, transport_headers=request.transport_headers, timeout=self.timeout, verify=request.verify, cert=request.cert, proxy=_proxies_dict(self.proxy)) diff --git a/tests/transport_tests.py b/tests/transport_tests.py index c23f1054f..d09a65c51 100644 --- a/tests/transport_tests.py +++ b/tests/transport_tests.py @@ -48,7 +48,7 @@ def set_up(self): def test_call(self, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -63,7 +63,7 @@ def test_call(self, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -134,7 +134,7 @@ def test_identifier(self, request): """ id 1234 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_filter(self, request): @@ -152,7 +152,7 @@ def test_filter(self, request): """ operation ^= prefix -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_limit_offset(self, request): @@ -169,10 +169,10 @@ def test_limit_offset(self, request): self.assertIn(""" resultLimit -""", kwargs['data']) +""".encode(), kwargs['data']) self.assertIn("""limit 10 -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_old_mask(self, request): @@ -194,7 +194,7 @@ def test_old_mask(self, request): nested -""", kwargs['data']) +""".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') def test_mask_call_no_mask_prefix(self, request): @@ -209,7 +209,7 @@ def test_mask_call_no_mask_prefix(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something.nested]", + "mask[something.nested]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -225,7 +225,7 @@ def test_mask_call_v2(self, request): args, kwargs = request.call_args self.assertIn( - "mask[something[nested]]", + "mask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -241,7 +241,7 @@ def test_mask_call_filteredMask(self, request): args, kwargs = request.call_args self.assertIn( - "filteredMask[something[nested]]", + "filteredMask[something[nested]]".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -256,7 +256,7 @@ def test_mask_call_v2_dot(self, request): self.transport(req) args, kwargs = request.call_args - self.assertIn("mask.something.nested", + self.assertIn("mask.something.nested".encode(), kwargs['data']) @mock.patch('SoftLayer.transports.requests.Session.request') @@ -287,7 +287,7 @@ def test_print_reproduceable(self): def test_ibm_id_call(self, auth, request): request.return_value = self.response - data = ''' + data = ''' getObject @@ -302,7 +302,7 @@ def test_ibm_id_call(self, auth, request): -''' +'''.encode() req = transports.Request() req.service = 'SoftLayer_Service' @@ -360,6 +360,57 @@ def test_call_large_number_response(self, request): resp = self.transport(req) self.assertEqual(resp[0]['bytesUsed'], 2666148982056) + @mock.patch('SoftLayer.transports.requests.Session.request') + def test_nonascii_characters(self, request): + request.return_value = self.response + hostname = 'testé' + data = ''' + +getObject + + + + +headers + + + + + + + + +hostname +testé + + + + + +'''.encode() + + req = transports.Request() + req.service = 'SoftLayer_Service' + req.method = 'getObject' + req.args = ({'hostname': hostname},) + req.transport_user = "testUser" + req.transport_password = "testApiKey" + resp = self.transport(req) + + request.assert_called_with('POST', + 'http://something9999999999999999999999.com/SoftLayer_Service', + headers={'Content-Type': 'application/xml', + 'User-Agent': consts.USER_AGENT}, + proxies=None, + data=data, + timeout=None, + cert=None, + verify=True, + auth=mock.ANY) + self.assertEqual(resp, []) + self.assertIsInstance(resp, transports.SoftLayerListResult) + self.assertEqual(resp.total_count, 10) + @mock.patch('SoftLayer.transports.requests.Session.request') @pytest.mark.parametrize( From dcbc9fe57f95480ff790bcbc8dc9ac37185ca0fc Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Wed, 1 Sep 2021 13:43:42 -0500 Subject: [PATCH 0735/1582] Fixing a bunch of tox related issues, moslty unspecified-encoding and use-list-literal errors --- SoftLayer/CLI/autoscale/__init__.py | 1 + SoftLayer/CLI/autoscale/edit.py | 2 +- SoftLayer/CLI/block/count.py | 2 +- SoftLayer/CLI/block/replication/partners.py | 22 +++---------------- SoftLayer/CLI/dns/zone_import.py | 2 +- SoftLayer/CLI/file/count.py | 2 +- SoftLayer/CLI/file/replication/partners.py | 24 +++------------------ SoftLayer/CLI/firewall/edit.py | 2 +- SoftLayer/CLI/hardware/edit.py | 2 +- SoftLayer/CLI/hardware/upgrade.py | 2 +- SoftLayer/CLI/order/quote.py | 2 +- SoftLayer/CLI/sshkey/add.py | 2 +- SoftLayer/CLI/sshkey/print.py | 2 +- SoftLayer/CLI/ssl/add.py | 8 +++---- SoftLayer/CLI/ssl/download.py | 2 +- SoftLayer/CLI/ssl/edit.py | 8 +++---- SoftLayer/CLI/storage_utils.py | 21 ++++++++++++++++++ SoftLayer/CLI/template.py | 4 ++-- SoftLayer/CLI/virt/create.py | 2 +- SoftLayer/CLI/virt/edit.py | 2 +- SoftLayer/CLI/virt/upgrade.py | 2 +- SoftLayer/config.py | 2 +- SoftLayer/fixtures/SoftLayer_Account.py | 2 +- SoftLayer/managers/vs_capacity.py | 2 +- 24 files changed, 55 insertions(+), 67 deletions(-) diff --git a/SoftLayer/CLI/autoscale/__init__.py b/SoftLayer/CLI/autoscale/__init__.py index e69de29bb..80cd82747 100644 --- a/SoftLayer/CLI/autoscale/__init__.py +++ b/SoftLayer/CLI/autoscale/__init__.py @@ -0,0 +1 @@ +"""Autoscale""" diff --git a/SoftLayer/CLI/autoscale/edit.py b/SoftLayer/CLI/autoscale/edit.py index c470aebbf..94e2165af 100644 --- a/SoftLayer/CLI/autoscale/edit.py +++ b/SoftLayer/CLI/autoscale/edit.py @@ -32,7 +32,7 @@ def cli(env, identifier, name, minimum, maximum, userdata, userfile, cpu, memory if userdata: virt_template['userData'] = [{"value": userdata}] elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: virt_template['userData'] = [{"value": userfile_obj.read()}] virt_template['startCpus'] = cpu virt_template['maxMemory'] = memory diff --git a/SoftLayer/CLI/block/count.py b/SoftLayer/CLI/block/count.py index 9f5fd35cf..ecfba0a53 100644 --- a/SoftLayer/CLI/block/count.py +++ b/SoftLayer/CLI/block/count.py @@ -25,7 +25,7 @@ def cli(env, sortby, datacenter): mask=mask) # cycle through all block volumes and count datacenter occurences. - datacenters = dict() + datacenters = {} for volume in block_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/block/replication/partners.py b/SoftLayer/CLI/block/replication/partners.py index f19be0af5..ade8f6b0f 100644 --- a/SoftLayer/CLI/block/replication/partners.py +++ b/SoftLayer/CLI/block/replication/partners.py @@ -6,26 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -COLUMNS = [ - column_helper.Column('ID', ('id',)), - column_helper.Column('Username', ('username',), mask="username"), - column_helper.Column('Account ID', ('accountId',), mask="accountId"), - column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), - column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), - column_helper.Column('Guest ID', ('guestId',), mask="guestId"), - column_helper.Column('Host ID', ('hostId',), mask="hostId"), -] - -DEFAULT_COLUMNS = [ - 'ID', - 'Username', - 'Account ID', - 'Capacity (GB)', - 'Hardware ID', - 'Guest ID', - 'Host ID' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/dns/zone_import.py b/SoftLayer/CLI/dns/zone_import.py index 7b47cdb12..e0e3d72d4 100644 --- a/SoftLayer/CLI/dns/zone_import.py +++ b/SoftLayer/CLI/dns/zone_import.py @@ -26,7 +26,7 @@ def cli(env, zonefile, dry_run): """Import zone based off a BIND zone file.""" manager = SoftLayer.DNSManager(env.client) - with open(zonefile) as zone_f: + with open(zonefile, encoding="utf-8") as zone_f: zone_contents = zone_f.read() zone, records, bad_lines = parse_zone_details(zone_contents) diff --git a/SoftLayer/CLI/file/count.py b/SoftLayer/CLI/file/count.py index 215d5c4d7..cb6ed1a0a 100644 --- a/SoftLayer/CLI/file/count.py +++ b/SoftLayer/CLI/file/count.py @@ -24,7 +24,7 @@ def cli(env, sortby, datacenter): file_volumes = file_manager.list_file_volumes(datacenter=datacenter, mask=mask) - datacenters = dict() + datacenters = {} for volume in file_volumes: service_resource = volume['serviceResource'] if 'datacenter' in service_resource: diff --git a/SoftLayer/CLI/file/replication/partners.py b/SoftLayer/CLI/file/replication/partners.py index 866248fdf..b48418b1a 100644 --- a/SoftLayer/CLI/file/replication/partners.py +++ b/SoftLayer/CLI/file/replication/partners.py @@ -6,28 +6,10 @@ from SoftLayer.CLI import columns as column_helper from SoftLayer.CLI import environment from SoftLayer.CLI import formatting +from SoftLayer.CLI import storage_utils -COLUMNS = [ - column_helper.Column('ID', ('id',)), - column_helper.Column('Username', ('username',), mask="username"), - column_helper.Column('Account ID', ('accountId',), mask="accountId"), - column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), - column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), - column_helper.Column('Guest ID', ('guestId',), mask="guestId"), - column_helper.Column('Host ID', ('hostId',), mask="hostId"), -] - -# In-line comment to avoid similarity flag with block version - -DEFAULT_COLUMNS = [ - 'ID', - 'Username', - 'Account ID', - 'Capacity (GB)', - 'Hardware ID', - 'Guest ID', - 'Host ID' -] +COLUMNS = storage_utils.REPLICATION_PARTNER_COLUMNS +DEFAULT_COLUMNS = storage_utils.REPLICATION_PARTNER_DEFAULT @click.command() diff --git a/SoftLayer/CLI/firewall/edit.py b/SoftLayer/CLI/firewall/edit.py index d2bb5bb2a..0e1f38dac 100644 --- a/SoftLayer/CLI/firewall/edit.py +++ b/SoftLayer/CLI/firewall/edit.py @@ -23,7 +23,7 @@ def parse_rules(content=None): :returns: a list of rules """ rules = content.split(DELIMITER) - parsed_rules = list() + parsed_rules = [] order = 1 for rule in rules: if rule.strip() == '': diff --git a/SoftLayer/CLI/hardware/edit.py b/SoftLayer/CLI/hardware/edit.py index dc1152c6f..32b1ba5d2 100644 --- a/SoftLayer/CLI/hardware/edit.py +++ b/SoftLayer/CLI/hardware/edit.py @@ -40,7 +40,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, public_speed if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() if tag: diff --git a/SoftLayer/CLI/hardware/upgrade.py b/SoftLayer/CLI/hardware/upgrade.py index a5b432e82..a3f197d24 100644 --- a/SoftLayer/CLI/hardware/upgrade.py +++ b/SoftLayer/CLI/hardware/upgrade.py @@ -51,7 +51,7 @@ def cli(env, identifier, memory, network, drive_controller, public_bandwidth, ad "This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_list = list() + disk_list = [] if add_disk: for guest_disk in add_disk: disks = {'description': 'add_disk', 'capacity': guest_disk[0], 'number': guest_disk[1]} diff --git a/SoftLayer/CLI/order/quote.py b/SoftLayer/CLI/order/quote.py index 498dbdc56..f129c02ad 100644 --- a/SoftLayer/CLI/order/quote.py +++ b/SoftLayer/CLI/order/quote.py @@ -43,7 +43,7 @@ def _parse_create_args(client, args): if args.get('userdata'): userdata = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: userdata = userfile.read() if userdata: for hardware in data['hardware']: diff --git a/SoftLayer/CLI/sshkey/add.py b/SoftLayer/CLI/sshkey/add.py index d80330d7c..570cfcea8 100644 --- a/SoftLayer/CLI/sshkey/add.py +++ b/SoftLayer/CLI/sshkey/add.py @@ -33,7 +33,7 @@ def cli(env, label, in_file, key, note): if key: key_text = key else: - with open(path.expanduser(in_file), 'rU') as key_file: + with open(path.expanduser(in_file), 'rU', encoding="utf-8") as key_file: key_text = key_file.read().strip() key_file.close() diff --git a/SoftLayer/CLI/sshkey/print.py b/SoftLayer/CLI/sshkey/print.py index 2bcdcd3be..2e1444d64 100644 --- a/SoftLayer/CLI/sshkey/print.py +++ b/SoftLayer/CLI/sshkey/print.py @@ -26,7 +26,7 @@ def cli(env, identifier, out_file): key = mgr.get_key(key_id) if out_file: - with open(path.expanduser(out_file), 'w') as pub_file: + with open(path.expanduser(out_file), 'w', encoding="utf-8") as pub_file: pub_file.write(key['key']) table = formatting.KeyValueTable(['name', 'value']) diff --git a/SoftLayer/CLI/ssl/add.py b/SoftLayer/CLI/ssl/add.py index 161b48c5e..9a579d899 100644 --- a/SoftLayer/CLI/ssl/add.py +++ b/SoftLayer/CLI/ssl/add.py @@ -28,16 +28,16 @@ def cli(env, crt, csr, icc, key, notes): 'certificateSigningRequest': '', 'notes': notes, } - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: body = file_csr.read() template['certificateSigningRequest'] = body - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: body = file_icc.read() template['intermediateCertificate'] = body diff --git a/SoftLayer/CLI/ssl/download.py b/SoftLayer/CLI/ssl/download.py index 77b5247b2..3c7c2dfb6 100644 --- a/SoftLayer/CLI/ssl/download.py +++ b/SoftLayer/CLI/ssl/download.py @@ -30,5 +30,5 @@ def cli(env, identifier): def write_cert(filename, content): """Writes certificate body to the given file path.""" - with open(filename, 'w') as cert_file: + with open(filename, 'w', encoding="utf-8") as cert_file: cert_file.write(content) diff --git a/SoftLayer/CLI/ssl/edit.py b/SoftLayer/CLI/ssl/edit.py index da899f34f..b6dc08e7b 100644 --- a/SoftLayer/CLI/ssl/edit.py +++ b/SoftLayer/CLI/ssl/edit.py @@ -24,16 +24,16 @@ def cli(env, identifier, crt, csr, icc, key, notes): """Edit SSL certificate.""" template = {'id': identifier} - with open(crt) as file_crt: + with open(crt, encoding="utf-8") as file_crt: if crt: template['certificate'] = file_crt.read() - with open(key) as file_key: + with open(key, encoding="utf-8") as file_key: if key: template['privateKey'] = file_key.read() - with open(csr) as file_csr: + with open(csr, encoding="utf-8") as file_csr: if csr: template['certificateSigningRequest'] = file_csr.read() - with open(icc) as file_icc: + with open(icc, encoding="utf-8") as file_icc: if icc: template['intermediateCertificate'] = file_icc.read() if notes: diff --git a/SoftLayer/CLI/storage_utils.py b/SoftLayer/CLI/storage_utils.py index aa24585eb..3d23e0941 100644 --- a/SoftLayer/CLI/storage_utils.py +++ b/SoftLayer/CLI/storage_utils.py @@ -144,3 +144,24 @@ def _format_name(obj): 'password', 'allowed_host_id', ] + + +REPLICATION_PARTNER_COLUMNS = [ + column_helper.Column('ID', ('id',)), + column_helper.Column('Username', ('username',), mask="username"), + column_helper.Column('Account ID', ('accountId',), mask="accountId"), + column_helper.Column('Capacity (GB)', ('capacityGb',), mask="capacityGb"), + column_helper.Column('Hardware ID', ('hardwareId',), mask="hardwareId"), + column_helper.Column('Guest ID', ('guestId',), mask="guestId"), + column_helper.Column('Host ID', ('hostId',), mask="hostId"), +] + +REPLICATION_PARTNER_DEFAULT = [ + 'ID', + 'Username', + 'Account ID', + 'Capacity (GB)', + 'Hardware ID', + 'Guest ID', + 'Host ID' +] diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 012644aa6..9ecbd7172 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -24,7 +24,7 @@ def __call__(self, ctx, param, value): if value is None: return - with open(os.path.expanduser(value), 'r') as file_handle: + with open(os.path.expanduser(value), 'r', encoding="utf-8") as file_handle: config = configparser.ConfigParser() ini_str = '[settings]\n' + file_handle.read() ini_fp = io.StringIO(ini_str) @@ -58,7 +58,7 @@ def export_to_template(filename, args, exclude=None): exclude.append('format') exclude.append('debug') - with open(filename, "w") as template_file: + with open(filename, "w", encoding="utf-8") as template_file: for k, val in args.items(): if val and k not in exclude: if isinstance(val, tuple): diff --git a/SoftLayer/CLI/virt/create.py b/SoftLayer/CLI/virt/create.py index 9d07f01d2..ad8b3b35b 100644 --- a/SoftLayer/CLI/virt/create.py +++ b/SoftLayer/CLI/virt/create.py @@ -118,7 +118,7 @@ def _parse_create_args(client, args): if args.get('userdata'): data['userdata'] = args['userdata'] elif args.get('userfile'): - with open(args['userfile'], 'r') as userfile: + with open(args['userfile'], 'r', encoding="utf-8") as userfile: data['userdata'] = userfile.read() # Get the SSH keys diff --git a/SoftLayer/CLI/virt/edit.py b/SoftLayer/CLI/virt/edit.py index 7c7e07db9..a72caa585 100644 --- a/SoftLayer/CLI/virt/edit.py +++ b/SoftLayer/CLI/virt/edit.py @@ -43,7 +43,7 @@ def cli(env, identifier, domain, userfile, tag, hostname, userdata, if userdata: data['userdata'] = userdata elif userfile: - with open(userfile, 'r') as userfile_obj: + with open(userfile, 'r', encoding="utf-8") as userfile_obj: data['userdata'] = userfile_obj.read() data['hostname'] = hostname diff --git a/SoftLayer/CLI/virt/upgrade.py b/SoftLayer/CLI/virt/upgrade.py index fdfa37822..45e60e573 100644 --- a/SoftLayer/CLI/virt/upgrade.py +++ b/SoftLayer/CLI/virt/upgrade.py @@ -42,7 +42,7 @@ def cli(env, identifier, cpu, private, memory, network, flavor, add_disk, resize if not (env.skip_confirmations or formatting.confirm("This action will incur charges on your account. Continue?")): raise exceptions.CLIAbort('Aborted') - disk_json = list() + disk_json = [] if memory: memory = int(memory / 1024) if resize_disk: diff --git a/SoftLayer/config.py b/SoftLayer/config.py index caa8def10..5ae8c7131 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -118,5 +118,5 @@ def write_config(configuration, config_file=None): if config_file is None: config_file = '~/.softlayer' config_file = os.path.expanduser(config_file) - with open(config_file, 'w') as file: + with open(config_file, 'w', encoding="utf-8") as file: configuration.write(file) diff --git a/SoftLayer/fixtures/SoftLayer_Account.py b/SoftLayer/fixtures/SoftLayer_Account.py index 7ca2d7e67..35216be76 100644 --- a/SoftLayer/fixtures/SoftLayer_Account.py +++ b/SoftLayer/fixtures/SoftLayer_Account.py @@ -14,7 +14,7 @@ 'globalIdentifier': 'F9329795-4220-4B0A-B970-C86B950667FA', 'id': 201, # 'name': 'private_image2', - 'name': u'a¬ሴ€耀', + 'name': 'a¬ሴ€耀', 'parentId': '', 'publicFlag': False, }] diff --git a/SoftLayer/managers/vs_capacity.py b/SoftLayer/managers/vs_capacity.py index 727a881b9..433f2b63a 100644 --- a/SoftLayer/managers/vs_capacity.py +++ b/SoftLayer/managers/vs_capacity.py @@ -89,7 +89,7 @@ def get_available_routers(self, dc=None): for region in regions: routers[region['keyname']] = [] for location in region['locations']: - location['location']['pods'] = list() + location['location']['pods'] = [] for pod in pods: if pod['datacenterName'] == location['location']['name']: location['location']['pods'].append(pod) From 849a1e4e0fead06e1aead249c3ff27d3211c61d0 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 17:09:07 -0400 Subject: [PATCH 0736/1582] Add sensor data to hardware --- SoftLayer/CLI/hardware/sensor.py | 69 ++++++++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + SoftLayer/fixtures/SoftLayer_Hardware.py | 28 ++++++++++ SoftLayer/managers/hardware.py | 4 ++ docs/cli/hardware.rst | 4 ++ tests/CLI/modules/server_tests.py | 8 +++ tests/managers/hardware_tests.py | 12 +++-- 7 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 SoftLayer/CLI/hardware/sensor.py diff --git a/SoftLayer/CLI/hardware/sensor.py b/SoftLayer/CLI/hardware/sensor.py new file mode 100644 index 000000000..cba0f097c --- /dev/null +++ b/SoftLayer/CLI/hardware/sensor.py @@ -0,0 +1,69 @@ +"""Hardware internal Sensor .""" +# :license: MIT, see LICENSE for more details. + +import SoftLayer +from SoftLayer.CLI import click +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting + + +@click.command() +@click.argument('identifier') +@click.option('--discrete', is_flag=True, default=False, help='Show discrete units associated hardware sensor') +@environment.pass_env +def cli(env, identifier, discrete): + """Retrieve a server’s hardware state via its internal sensors.""" + mgr = SoftLayer.HardwareManager(env.client) + sensors = mgr.get_sensors(identifier) + + temperature_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='temperature Degree c') + + volts_table = formatting.Table(["sensor", "status", "Reading", "min", "Max"], + title='volts') + + watts_table = formatting.Table(["sensor", "status", "Reading"], + title='Watts') + + rpm_table = formatting.Table(["sensor", "status", "Reading", "min"], + title='RPM') + + discrete_table = formatting.Table(["sensor", "status", "Reading"], + title='discrete') + + for sensor in sensors: + if sensor.get('sensorUnits') == 'degrees C': + temperature_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('upperNonCritical'), + sensor.get('upperCritical')]) + + if sensor.get('sensorUnits') == 'volts': + volts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerNonCritical'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'Watts': + watts_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + + if sensor.get('sensorUnits') == 'RPM': + rpm_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading'), + sensor.get('lowerCritical')]) + + if sensor.get('sensorUnits') == 'discrete': + discrete_table.add_row([sensor.get('sensorId'), + sensor.get('status'), + sensor.get('sensorReading')]) + env.fout(temperature_table) + env.fout(rpm_table) + env.fout(volts_table) + env.fout(watts_table) + if discrete: + env.fout(discrete_table) diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index a6e42539c..fd53cc43c 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -272,6 +272,7 @@ ('hardware:dns-sync', 'SoftLayer.CLI.hardware.dns:cli'), ('hardware:storage', 'SoftLayer.CLI.hardware.storage:cli'), ('hardware:upgrade', 'SoftLayer.CLI.hardware.upgrade:cli'), + ('hardware:sensor', 'SoftLayer.CLI.hardware.sensor:cli'), ('securitygroup', 'SoftLayer.CLI.securitygroup'), ('securitygroup:list', 'SoftLayer.CLI.securitygroup.list:cli'), diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index fd6bf9535..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -59,3 +59,31 @@ } allowAccessToNetworkStorageList = True + +getSensorData = [ + { + "sensorId": "Ambient 1 Temperature", + "sensorReading": "25.000", + "sensorUnits": "degrees C", + "status": "ok", + "upperCritical": "43.000", + "upperNonCritical": "41.000", + "upperNonRecoverable": "46.000" + }, + { + "lowerCritical": "3500.000", + "sensorId": "Fan 1 Tach", + "sensorReading": "6580.000", + "sensorUnits": "RPM", + "status": "ok" + }, { + "sensorId": "IPMI Watchdog", + "sensorReading": "0x0", + "sensorUnits": "discrete", + "status": "0x0080" + }, { + "sensorId": "Avg Power", + "sensorReading": "70.000", + "sensorUnits": "Watts", + "status": "ok" + }] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0f410b322..fe494f83d 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -1098,6 +1098,10 @@ def get_components(self, hardware_id, mask=None, filter_component=None): return self.client.call('Hardware_Server', 'getComponents', mask=mask, filter=filter_component, id=hardware_id) + def get_sensors(self, hardware_id): + """Returns Hardware sensor data""" + return self.client.call('Hardware', 'getSensorData', id=hardware_id) + def _get_bandwidth_key(items, hourly=True, no_public=False, location=None): """Picks a valid Bandwidth Item, returns the KeyName""" diff --git a/docs/cli/hardware.rst b/docs/cli/hardware.rst index aa95d9141..1f8375cfe 100644 --- a/docs/cli/hardware.rst +++ b/docs/cli/hardware.rst @@ -127,3 +127,7 @@ This function updates the firmware of a server. If already at the latest version .. click:: SoftLayer.CLI.hardware.upgrade:cli :prog: hardware upgrade :show-nested: + +.. click:: SoftLayer.CLI.hardware.sensor:cli + :prog: hardware sensor + :show-nested: diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index 3b5498d1a..b558811a2 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -996,3 +996,11 @@ def test_upgrade(self, confirm_mock): def test_components(self): result = self.run_command(['hardware', 'detail', '100', '--components']) self.assert_no_fail(result) + + def test_sensor(self): + result = self.run_command(['hardware', 'sensor', '100']) + self.assert_no_fail(result) + + def test_sensor_discrete(self): + result = self.run_command(['hardware', 'sensor', '100', '--discrete']) + self.assert_no_fail(result) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 307b38250..51a6e510d 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): @@ -948,6 +948,10 @@ def test_get_components(self): self.assertEqual(result[0]['hardwareComponentModel']['name'], 'IMM2 - Onboard') self.assertEqual(result[0]['hardwareComponentModel']['firmwares'][0]['version'], '5.60') + def test_sensor(self): + self.hardware.get_sensors(100) + self.assert_called_with('SoftLayer_Hardware', 'getSensorData') + class HardwareHelperTests(testing.TestCase): From 9317fe356b83d1fea1638f6b7db1b1f28dc6f842 Mon Sep 17 00:00:00 2001 From: caberos Date: Thu, 2 Sep 2021 18:01:26 -0400 Subject: [PATCH 0737/1582] fix the tox tool --- tests/managers/hardware_tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/managers/hardware_tests.py b/tests/managers/hardware_tests.py index 51a6e510d..a9eada76c 100644 --- a/tests/managers/hardware_tests.py +++ b/tests/managers/hardware_tests.py @@ -557,10 +557,10 @@ def test_edit(self): self.assert_called_with('SoftLayer_Hardware_Server', 'editObject', args=({ - 'hostname': 'new-host', - 'domain': 'new.sftlyr.ws', - 'notes': 'random notes', - },), + 'hostname': 'new-host', + 'domain': 'new.sftlyr.ws', + 'notes': 'random notes', + },), identifier=100) def test_rescue(self): From 6b17d25b03ba231bd4618b2758d49b3d490ca551 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 15:44:34 -0400 Subject: [PATCH 0738/1582] #1545 remove erroneous print statement in unplanned_event_table function --- SoftLayer/CLI/account/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/CLI/account/events.py b/SoftLayer/CLI/account/events.py index b5d8960cb..7d4803b42 100644 --- a/SoftLayer/CLI/account/events.py +++ b/SoftLayer/CLI/account/events.py @@ -69,7 +69,6 @@ def unplanned_event_table(events): unplanned_table.align['Subject'] = 'l' unplanned_table.align['Impacted Resources'] = 'l' for event in events: - print(event.get('modifyDate')) unplanned_table.add_row([ event.get('id'), event.get('systemTicketId'), From caa096f4d3cac7735808657ed087022797db5d00 Mon Sep 17 00:00:00 2001 From: ATGE Date: Thu, 16 Sep 2021 16:23:27 -0400 Subject: [PATCH 0739/1582] #1545 add test to validate cli account event json output --- tests/CLI/modules/account_tests.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/CLI/modules/account_tests.py b/tests/CLI/modules/account_tests.py index fe9c11b0b..8428d3306 100644 --- a/tests/CLI/modules/account_tests.py +++ b/tests/CLI/modules/account_tests.py @@ -4,6 +4,8 @@ Tests for the user cli command """ +import json + from SoftLayer.fixtures import SoftLayer_Account as SoftLayer_Account from SoftLayer import testing @@ -37,8 +39,20 @@ def test_event_ack_all(self): self.assert_called_with(self.SLNOE, 'getAllObjects') self.assert_called_with(self.SLNOE, 'acknowledgeNotification', identifier=1234) - # slcli account invoice-detail + def test_event_jsonraw_output(self): + # https://github.com/softlayer/softlayer-python/issues/1545 + command = '--format jsonraw account events' + command_params = command.split() + result = self.run_command(command_params) + json_text_tables = result.stdout.split('\n') + # removing an extra item due to an additional Newline at the end of the output + json_text_tables.pop() + # each item in the json_text_tables should be a list + for json_text_table in json_text_tables: + json_table = json.loads(json_text_table) + self.assertIsInstance(json_table, list) + # slcli account invoice-detail def test_invoice_detail(self): result = self.run_command(['account', 'invoice-detail', '1234']) self.assert_no_fail(result) From e94ee4d64ec0b939de41392096943d48bf7b2c20 Mon Sep 17 00:00:00 2001 From: Christopher Gallo Date: Fri, 17 Sep 2021 14:39:48 -0500 Subject: [PATCH 0740/1582] Ignoring f-string related messages for tox for now --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 73b9a0007..54cfb633d 100644 --- a/tox.ini +++ b/tox.ini @@ -40,6 +40,7 @@ commands = -d consider-using-in \ -d consider-using-dict-comprehension \ -d useless-import-alias \ + -d consider-using-f-string \ --max-args=25 \ --max-branches=20 \ --max-statements=65 \ @@ -60,4 +61,4 @@ commands = [testenv:docs] commands = - python ./docCheck.py \ No newline at end of file + python ./docCheck.py From 3090070a28e67b149157302f10337b9e93fb0075 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:33:10 -0400 Subject: [PATCH 0741/1582] #1541 fix to loadbal details duplicate columns error in members table --- SoftLayer/CLI/loadbal/detail.py | 108 +++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 37 deletions(-) diff --git a/SoftLayer/CLI/loadbal/detail.py b/SoftLayer/CLI/loadbal/detail.py index 35e10f376..6712c3ce2 100644 --- a/SoftLayer/CLI/loadbal/detail.py +++ b/SoftLayer/CLI/loadbal/detail.py @@ -29,12 +29,33 @@ def lbaas_table(this_lb): table.align['value'] = 'l' table.add_row(['Id', this_lb.get('id')]) table.add_row(['UUI', this_lb.get('uuid')]) + table.add_row(['Name', this_lb.get('name')]) table.add_row(['Address', this_lb.get('address')]) + table.add_row(['Type', SoftLayer.LoadBalancerManager.TYPE.get(this_lb.get('type'))]) table.add_row(['Location', utils.lookup(this_lb, 'datacenter', 'longName')]) table.add_row(['Description', this_lb.get('description')]) - table.add_row(['Name', this_lb.get('name')]) table.add_row(['Status', "{} / {}".format(this_lb.get('provisioningStatus'), this_lb.get('operatingStatus'))]) + listener_table, pools = get_listener_table(this_lb) + table.add_row(['Protocols', listener_table]) + + member_table = get_member_table(this_lb, pools) + table.add_row(['Members', member_table]) + + hp_table = get_hp_table(this_lb) + table.add_row(['Health Checks', hp_table]) + + l7pool_table = get_l7pool_table(this_lb) + table.add_row(['L7 Pools', l7pool_table]) + + ssl_table = get_ssl_table(this_lb) + table.add_row(['Ciphers', ssl_table]) + + return table + + +def get_hp_table(this_lb): + """Generates a table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_HealthMonitor/ hp_table = formatting.Table(['UUID', 'Interval', 'Retries', 'Type', 'Timeout', 'Modify', 'Active']) for health in this_lb.get('healthMonitors', []): @@ -47,44 +68,17 @@ def lbaas_table(this_lb): utils.clean_time(health.get('modifyDate')), health.get('provisioningStatus') ]) - table.add_row(['Checks', hp_table]) + return hp_table - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ - l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) - for layer7 in this_lb.get('l7Pools', []): - l7_table.add_row([ - layer7.get('id'), - layer7.get('uuid'), - layer7.get('loadBalancingAlgorithm'), - layer7.get('name'), - layer7.get('protocol'), - utils.clean_time(layer7.get('modifyDate')), - layer7.get('provisioningStatus') - ]) - table.add_row(['L7 Pools', l7_table]) - - pools = {} - # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ - listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) - for listener in this_lb.get('listeners', []): - pool = listener.get('defaultPool') - priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) - pools[pool['uuid']] = priv_map - mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) - listener_table.add_row([ - listener.get('uuid'), - listener.get('connectionLimit', 'None'), - mapping, - pool.get('loadBalancingAlgorithm', 'None'), - utils.clean_time(listener.get('modifyDate')), - listener.get('provisioningStatus') - ]) - table.add_row(['Pools', listener_table]) +def get_member_table(this_lb, pools): + """Generates a members table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Member/ member_col = ['UUID', 'Address', 'Weight', 'Modify', 'Active'] + counter = 0 for uuid in pools.values(): - member_col.append(uuid) + member_col.append(f'P{counter}-> {uuid}') + counter += 1 member_table = formatting.Table(member_col) for member in this_lb.get('members', []): row = [ @@ -97,14 +91,54 @@ def lbaas_table(this_lb): for uuid in pools: row.append(get_member_hp(this_lb.get('health'), member.get('uuid'), uuid)) member_table.add_row(row) - table.add_row(['Members', member_table]) + return member_table + +def get_ssl_table(this_lb): + """Generates a ssl table from a list of LBaaS devices""" # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_SSLCipher/ ssl_table = formatting.Table(['Id', 'Name']) for ssl in this_lb.get('sslCiphers', []): ssl_table.add_row([ssl.get('id'), ssl.get('name')]) - table.add_row(['Ciphers', ssl_table]) - return table + return ssl_table + + +def get_listener_table(this_lb): + """Generates a protocols table from a list of LBaaS devices""" + pools = {} + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_Listener/ + listener_table = formatting.Table(['UUID', 'Max Connection', 'Mapping', 'Balancer', 'Modify', 'Active']) + for listener in this_lb.get('listeners', []): + pool = listener.get('defaultPool') + priv_map = "{}:{}".format(pool['protocol'], pool['protocolPort']) + pools[pool['uuid']] = priv_map + mapping = "{}:{} -> {}".format(listener.get('protocol'), listener.get('protocolPort'), priv_map) + listener_table.add_row([ + listener.get('uuid'), + listener.get('connectionLimit', 'None'), + mapping, + pool.get('loadBalancingAlgorithm', 'None'), + utils.clean_time(listener.get('modifyDate')), + listener.get('provisioningStatus') + ]) + return listener_table, pools + + +def get_l7pool_table(this_lb): + """Generates a l7Pools table from a list of LBaaS devices""" + # https://sldn.softlayer.com/reference/datatypes/SoftLayer_Network_LBaaS_L7Pool/ + l7_table = formatting.Table(['Id', 'UUID', 'Balancer', 'Name', 'Protocol', 'Modify', 'Active']) + for layer7 in this_lb.get('l7Pools', []): + l7_table.add_row([ + layer7.get('id'), + layer7.get('uuid'), + layer7.get('loadBalancingAlgorithm'), + layer7.get('name'), + layer7.get('protocol'), + utils.clean_time(layer7.get('modifyDate')), + layer7.get('provisioningStatus') + ]) + return l7_table def get_member_hp(checks, member_uuid, pool_uuid): From a7690bd7d813fe6038c9a9e1c9160af27b99425b Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:36:10 -0400 Subject: [PATCH 0742/1582] 1541 add const type of load balancer as UI, and ibmcloud cli shows --- SoftLayer/managers/load_balancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 3ffa09594..051d2a4e7 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -18,6 +18,11 @@ class LoadBalancerManager(utils.IdentifierMixin, object): :param SoftLayer.API.BaseClient client: the client instance """ + TYPE = { + 1: "Public to Private", + 0: "Private to Private", + 2: "Public to Public", + } def __init__(self, client): self.client = client From 474f5fb70bcc711f54b744f286ba575b4288d434 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 17 Sep 2021 20:38:46 -0400 Subject: [PATCH 0743/1582] 1541 add validation of some fields to loadbal detail test --- .../fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py | 11 +++++++++++ tests/CLI/modules/loadbal_tests.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py index 70e8b64cb..d2a919ae1 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_LoadBalancer.py @@ -58,6 +58,17 @@ 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8f', } }, + { + 'clientTimeout': 15, + 'defaultPool': { + 'healthMonitor': { + 'uuid': '222222ab-bbcc-4f32-9b31-1b6d3a1959c0' + }, + 'protocol': 'HTTP', + 'protocolPort': 256, + 'uuid': 'ab1a1abc-0e83-4690-b5d4-1359625dba8x', + } + }, {'connectionLimit': None, 'createDate': '2019-08-21T17:19:25-04:00', 'defaultPool': {'createDate': '2019-08-21T17:19:25-04:00', diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 0fba53cea..8c5cb1147 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -215,6 +215,9 @@ def test_lb_health_update_fails(self, update_lb_health_monitors): def test_lb_detail(self): result = self.run_command(['lb', 'detail', '1111111']) self.assert_no_fail(result) + self.assertIn('Id', result.output) + self.assertIn('UUI', result.output) + self.assertIn('Address', result.output) def test_lb_detail_by_name(self): name = SoftLayer_Network_LBaaS_LoadBalancer.getObject.get('name') From 046d9be7cb44f74ac7cccd331fa8ac86b14385f8 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Wed, 6 Oct 2021 16:52:27 +0200 Subject: [PATCH 0744/1582] fix: initialized accountmanger closes:https://github.com/softlayer/softlayer-python/issues/1551 --- SoftLayer/managers/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 1b2ddf6f9..2a8955408 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -7,6 +7,7 @@ :license: MIT, see LICENSE for more details. """ +from SoftLayer.managers.account import AccountManager from SoftLayer.managers.block import BlockStorageManager from SoftLayer.managers.cdn import CDNManager from SoftLayer.managers.dedicated_host import DedicatedHostManager @@ -33,6 +34,7 @@ from SoftLayer.managers.vs_placement import PlacementManager __all__ = [ + 'AccountManager', 'BlockStorageManager', 'CapacityManager', 'CDNManager', From 297e0de57bdf1d21b6652b3da24c5583e774fc13 Mon Sep 17 00:00:00 2001 From: Gonza Rafuls Date: Fri, 8 Oct 2021 11:26:51 +0200 Subject: [PATCH 0745/1582] fix: SoftLayerAPIError import on managers/account.py --- SoftLayer/managers/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/managers/account.py b/SoftLayer/managers/account.py index e6337e716..51c9c889c 100644 --- a/SoftLayer/managers/account.py +++ b/SoftLayer/managers/account.py @@ -8,7 +8,7 @@ import logging -from SoftLayer import SoftLayerAPIError +from SoftLayer.exceptions import SoftLayerAPIError from SoftLayer import utils # Invalid names are ignored due to long method names and short argument names From 42d9d7ae5722848f6321f334b964717a2fcf5eb4 Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:27 -0400 Subject: [PATCH 0746/1582] #1550 add loadbal l7policies --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 56 +++++++++++++++++++ SoftLayer/CLI/routes.py | 1 + .../SoftLayer_Network_LBaaS_Listener.py | 23 ++++++++ SoftLayer/managers/load_balancer.py | 26 +++++++++ tests/CLI/modules/loadbal_tests.py | 10 ++++ tests/managers/loadbal_tests.py | 10 ++++ 6 files changed, 126 insertions(+) create mode 100644 SoftLayer/CLI/loadbal/layer7_policy_list.py diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py new file mode 100644 index 000000000..80c408ed1 --- /dev/null +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -0,0 +1,56 @@ +"""List Layer7 policies""" +import click + +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils + + +@click.command() +@click.option('--protocol-id', '-p', + required=False, + type=int, + help="Front-end Protocol identifier") +@environment.pass_env +def policies(env, protocol_id): + """List policies of the front-end protocol (listener).""" + mgr = SoftLayer.LoadBalancerManager(env.client) + + if protocol_id: + l7policies = mgr.get_l7policies(protocol_id) + table = generate_l7policies_table(l7policies, protocol_id) + else: + l7policies = mgr.get_all_l7policies() + table = l7policies_table(l7policies) + env.fout(table) + + +def generate_l7policies_table(l7policies, identifier): + """Takes a list of Layer7 policies and makes a table""" + table = formatting.Table([ + 'Id', 'UUID', 'Name', 'Action', 'Redirect', 'Priority', 'Create Date' + ], title=f"Layer7 policies - protocol ID {identifier}") + + table.align['Name'] = 'l' + table.align['Action'] = 'l' + table.align['Redirect'] = 'l' + for l7policy in sorted(l7policies, key=lambda data: data.get('priority')): + table.add_row([ + l7policy.get('id'), + l7policy.get('uuid'), + l7policy.get('name'), + l7policy.get('action'), + l7policy.get('redirectL7PoolId') or l7policy.get('redirectUrl') or formatting.blank(), + l7policy.get('priority'), + l7policy.get('createDate'), + ]) + return table + + +def l7policies_table(listeners): + """Takes a dict of (protocols: policies list) and makes a list of tables""" + tables = [] + for listener_id, list_policy in listeners.items(): + tables.append(generate_l7policies_table(list_policy, listener_id)) + return tables diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py index fd53cc43c..c051ad3fd 100644 --- a/SoftLayer/CLI/routes.py +++ b/SoftLayer/CLI/routes.py @@ -204,6 +204,7 @@ ('loadbal:health', 'SoftLayer.CLI.loadbal.health:cli'), ('loadbal:member-add', 'SoftLayer.CLI.loadbal.members:add'), ('loadbal:member-del', 'SoftLayer.CLI.loadbal.members:remove'), + ('loadbal:l7policies', 'SoftLayer.CLI.loadbal.layer7_policy_list:policies'), ('loadbal:pool-add', 'SoftLayer.CLI.loadbal.pools:add'), ('loadbal:pool-edit', 'SoftLayer.CLI.loadbal.pools:edit'), ('loadbal:pool-del', 'SoftLayer.CLI.loadbal.pools:delete'), diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index 57a2459e8..ba814c730 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -41,3 +41,26 @@ 'name': 'ams01', 'statusId': 2 }} + +getL7Policies = [ + {'action': 'REJECT', + 'createDate': '2021-09-08T15:08:35-06:00', + 'id': 123456, + 'modifyDate': None, + 'name': 'test-reject', + 'priority': 2, + 'redirectL7PoolId': None, + 'uuid': '123mock-1234-43c9-b659-12345678mock' + }, + {'action': 'REDIRECT_HTTPS', + 'createDate': '2021-09-08T15:03:53-06:00', + 'id': 432922, + 'modifyDate': None, + 'name': 'test-policy-https-1', + 'priority': 0, + 'redirectL7PoolId': None, + 'redirectUrl': 'url-test-uuid-mock-1234565', + 'uuid': 'test-uuid-mock-1234565' + } +] + diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index 051d2a4e7..c2c6b20de 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -160,6 +160,32 @@ def add_lb_listener(self, identifier, listener): return self.client.call('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', identifier, [listener]) + def get_l7policies(self, identifier): + """Gets Layer7 policies from a listener + + :param identifier: id + """ + + return self.client.call('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', id=identifier) + + def get_all_l7policies(self): + """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). + """ + + mask = 'mask[listeners[l7Policies]]' + lbaas = self.get_lbaas(mask=mask) + listeners = [] + for lb in lbaas: + listeners.extend(lb.get('listeners')) + policies = {} + for protocol in listeners: + if protocol.get('l7Policies'): + listener_id = protocol.get('id') + l7policies = protocol.get('l7Policies') + policies[listener_id] = l7policies + return policies + def add_lb_l7_pool(self, identifier, pool, members, health, session): """Creates a new l7 pool for a LBaaS instance diff --git a/tests/CLI/modules/loadbal_tests.py b/tests/CLI/modules/loadbal_tests.py index 8c5cb1147..cbba6266c 100644 --- a/tests/CLI/modules/loadbal_tests.py +++ b/tests/CLI/modules/loadbal_tests.py @@ -181,6 +181,16 @@ def test_lb_member_del(self, click): self.assert_no_fail(result) click.secho.assert_called_with(output, fg='green') + def test_lb_l7policies_list(self): + command = 'loadbal l7policies' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + + def test_lb_l7policies_protocol_list(self): + command = 'loadbal l7policies -p 123456' + result = self.run_command(command.split(' ')) + self.assert_no_fail(result) + @mock.patch('SoftLayer.CLI.loadbal.health.click') def test_lb_health_manage(self, click): lb_id = '1111111' diff --git a/tests/managers/loadbal_tests.py b/tests/managers/loadbal_tests.py index d8058edcc..7f580474a 100644 --- a/tests/managers/loadbal_tests.py +++ b/tests/managers/loadbal_tests.py @@ -110,6 +110,16 @@ def test_add_lb_listener(self): self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'updateLoadBalancerProtocols', args=(uuid, [listener])) + def test_get_l7policies(self): + my_id = 1111111 + self.lb_mgr.get_l7policies(my_id) + self.assert_called_with('SoftLayer_Network_LBaaS_Listener', 'getL7Policies', identifier=my_id) + + def test_get_all_l7policies(self): + policies = self.lb_mgr.get_all_l7policies() + self.assert_called_with('SoftLayer_Network_LBaaS_LoadBalancer', 'getAllObjects') + self.assertIsInstance(policies, dict) + def test_add_lb_l7_pool(self): uuid = 'aa-bb-cc' pool = {'id': 1} From f621b6f47061dfdad9471c68178db824bd02b7fc Mon Sep 17 00:00:00 2001 From: ATGE Date: Mon, 11 Oct 2021 20:46:56 -0400 Subject: [PATCH 0747/1582] #1550 add loadbal l7policies docs --- docs/cli/loadbal.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/cli/loadbal.rst b/docs/cli/loadbal.rst index a4116b877..e019e2aa0 100644 --- a/docs/cli/loadbal.rst +++ b/docs/cli/loadbal.rst @@ -40,6 +40,9 @@ LBaaS Commands .. click:: SoftLayer.CLI.loadbal.pools:l7pool_del :prog: loadbal l7pool-del :show-nested: +.. click:: SoftLayer.CLI.loadbal.layer7_policy_list:policies + :prog: loadbal l7policies + :show-nested: .. click:: SoftLayer.CLI.loadbal.order:order :prog: loadbal order :show-nested: From 26f2052f2054d53a731280838c32505cfebf2623 Mon Sep 17 00:00:00 2001 From: ATGE Date: Tue, 12 Oct 2021 11:14:32 -0400 Subject: [PATCH 0748/1582] #1550 fix tox issues --- SoftLayer/CLI/loadbal/layer7_policy_list.py | 1 - SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py | 1 - SoftLayer/managers/load_balancer.py | 5 +++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/loadbal/layer7_policy_list.py b/SoftLayer/CLI/loadbal/layer7_policy_list.py index 80c408ed1..563d1c35f 100644 --- a/SoftLayer/CLI/loadbal/layer7_policy_list.py +++ b/SoftLayer/CLI/loadbal/layer7_policy_list.py @@ -4,7 +4,6 @@ import SoftLayer from SoftLayer.CLI import environment from SoftLayer.CLI import formatting -from SoftLayer import utils @click.command() diff --git a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py index ba814c730..69cee2113 100644 --- a/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py +++ b/SoftLayer/fixtures/SoftLayer_Network_LBaaS_Listener.py @@ -63,4 +63,3 @@ 'uuid': 'test-uuid-mock-1234565' } ] - diff --git a/SoftLayer/managers/load_balancer.py b/SoftLayer/managers/load_balancer.py index c2c6b20de..f7f4cedd5 100644 --- a/SoftLayer/managers/load_balancer.py +++ b/SoftLayer/managers/load_balancer.py @@ -170,14 +170,15 @@ def get_l7policies(self, identifier): def get_all_l7policies(self): """Gets all Layer7 policies + :returns: Dictionary of (protocol_id: policies list). """ mask = 'mask[listeners[l7Policies]]' lbaas = self.get_lbaas(mask=mask) listeners = [] - for lb in lbaas: - listeners.extend(lb.get('listeners')) + for load_bal in lbaas: + listeners.extend(load_bal.get('listeners')) policies = {} for protocol in listeners: if protocol.get('l7Policies'): From 677f4349abf21b3edd4a6bcd23609b6a0bb0c979 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Mon, 18 Oct 2021 09:51:24 +0530 Subject: [PATCH 0749/1582] 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 0750/1582] 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 0751/1582] 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 0752/1582] 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 0753/1582] updating get command --- docs/cli/block.rst | 2 +- docs/cli/file.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli/block.rst b/docs/cli/block.rst index b5655709f..2f3cbfcfc 100644 --- a/docs/cli/block.rst +++ b/docs/cli/block.rst @@ -153,5 +153,5 @@ Block Commands :show-nested: .. click:: SoftLayer.CLI.block.snapshot.get_notify_status:cli - :prog: block snapshot-get-notification + :prog: block snapshot-get-notification-status :show-nested: diff --git a/docs/cli/file.rst b/docs/cli/file.rst index 7ecc0bc75..93b5c5331 100644 --- a/docs/cli/file.rst +++ b/docs/cli/file.rst @@ -132,6 +132,6 @@ File Commands :show-nested: .. click:: SoftLayer.CLI.file.snapshot.get_notify_status:cli - :prog: file snapshot-get-notification + :prog: file snapshot-get-notification-status :show-nested: From 564e2417bb2d01c91594359e0a200000032f5a29 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 14:13:27 -0400 Subject: [PATCH 0754/1582] #1555 Fix hw billing reports 0 items --- SoftLayer/CLI/hardware/billing.py | 2 +- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/hardware/billing.py b/SoftLayer/CLI/hardware/billing.py index 55c68c485..6989489bf 100644 --- a/SoftLayer/CLI/hardware/billing.py +++ b/SoftLayer/CLI/hardware/billing.py @@ -31,7 +31,7 @@ def cli(env, identifier): table.add_row(['Provision Date', utils.lookup(result, 'billingItem', 'provisionDate')]) price_table = formatting.Table(['Item', 'Recurring Price']) - for item in utils.lookup(result, 'billingItem', 'children') or []: + for item in utils.lookup(result, 'billingItem', 'nextInvoiceChildren') or []: price_table.add_row([item['description'], item['nextInvoiceTotalRecurringAmount']]) table.add_row(['prices', price_table]) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 770de045c..18fc5a7d5 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'children': [ + 'nextInvoiceChildren': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { From b3d28a262bea0b092a5d71e1168aa9864609c686 Mon Sep 17 00:00:00 2001 From: ATGE Date: Fri, 22 Oct 2021 15:16:40 -0400 Subject: [PATCH 0755/1582] #1555 update hw billing test --- SoftLayer/fixtures/SoftLayer_Hardware.py | 2 +- tests/CLI/modules/server_tests.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/SoftLayer/fixtures/SoftLayer_Hardware.py b/SoftLayer/fixtures/SoftLayer_Hardware.py index 18fc5a7d5..770de045c 100644 --- a/SoftLayer/fixtures/SoftLayer_Hardware.py +++ b/SoftLayer/fixtures/SoftLayer_Hardware.py @@ -7,7 +7,7 @@ 'id': 6327, 'recurringFee': 1.54, 'nextInvoiceTotalRecurringAmount': 16.08, - 'nextInvoiceChildren': [ + 'children': [ {'description': 'test', 'nextInvoiceTotalRecurringAmount': 1}, ], 'orderItem': { diff --git a/tests/CLI/modules/server_tests.py b/tests/CLI/modules/server_tests.py index b558811a2..d688a6a90 100644 --- a/tests/CLI/modules/server_tests.py +++ b/tests/CLI/modules/server_tests.py @@ -868,19 +868,25 @@ def test_hardware_storage(self): def test_billing(self): result = self.run_command(['hw', 'billing', '123456']) - billing_json = { + hardware_server_billing = { 'Billing Item Id': 6327, 'Id': '123456', 'Provision Date': None, 'Recurring Fee': 1.54, 'Total': 16.08, - 'prices': [{ - 'Item': 'test', - 'Recurring Price': 1 - }] + 'prices': [ + { + 'Item': 'test', + 'Recurring Price': 1 + }, + { + 'Item': 'test2', + 'Recurring Price': 2 + }, + ] } self.assert_no_fail(result) - self.assertEqual(json.loads(result.output), billing_json) + self.assertEqual(json.loads(result.output), hardware_server_billing) @mock.patch('SoftLayer.CLI.formatting.confirm') def test_create_hw_no_confirm(self, confirm_mock): From da4593ec14163a5ec46195b486aa1b78d62e2576 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Sat, 23 Oct 2021 12:33:49 +0530 Subject: [PATCH 0756/1582] Incorporating review comments --- .../CLI/block/snapshot/set_notify_status.py | 23 +++++++++--------- .../CLI/file/snapshot/set_notify_status.py | 24 +++++++++---------- SoftLayer/managers/storage.py | 18 ++++++++++---- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/SoftLayer/CLI/block/snapshot/set_notify_status.py b/SoftLayer/CLI/block/snapshot/set_notify_status.py index cdb260c89..9adffec87 100644 --- a/SoftLayer/CLI/block/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/block/snapshot/set_notify_status.py @@ -9,22 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable', required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - block_manager = SoftLayer.BlockStorageManager(env.client) - disabled = block_manager.set_block_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + status = block_manager.set_block_volume_snapshot_notification( + volume_id, enabled) + + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enabled, volume_id)) diff --git a/SoftLayer/CLI/file/snapshot/set_notify_status.py b/SoftLayer/CLI/file/snapshot/set_notify_status.py index 112939cc3..80b8494fa 100644 --- a/SoftLayer/CLI/file/snapshot/set_notify_status.py +++ b/SoftLayer/CLI/file/snapshot/set_notify_status.py @@ -9,23 +9,23 @@ @click.command() @click.argument('volume_id') -@click.option( - '--notification_flag', +@click.option('--enable/--disable', default=True, help= - 'Enable / disable sending sending notifications for snapshots space usage threshold warning [True|False]', + 'Enable/Disable snapshot usage warning notification. Use `slcli block snapshot-set-notification volumeId --enable` to enable' , required=True) @environment.pass_env -def cli(env, volume_id, notification_flag): +def cli(env, volume_id, enable): """Enables/Disables snapshot space usage threshold warning for a given volume""" - - if (notification_flag not in ['True', 'False']): - raise exceptions.CLIAbort('--notification-flag must be True or False') - file_manager = SoftLayer.FileStorageManager(env.client) - disabled = file_manager.set_file_volume_snapshot_notification( - volume_id, notification_flag) - if disabled: + if enable: + enabled = 'True' + else: + enabled = 'False' + + status = file_manager.set_file_volume_snapshot_notification( + volume_id, enabled) + if status: click.echo( 'Snapshots space usage threshold warning notification has bee set to %s for volume %s' - % (notification - flag, volume_id)) + % (enable, volume_id)) \ No newline at end of file diff --git a/SoftLayer/managers/storage.py b/SoftLayer/managers/storage.py index 4ac9a6ff7..734e8f9e2 100644 --- a/SoftLayer/managers/storage.py +++ b/SoftLayer/managers/storage.py @@ -112,15 +112,23 @@ def get_volume_snapshot_list(self, volume_id, **kwargs): kwargs['mask'] = ','.join(items) return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs) - def set_volume_snapshot_notification(self, volume_id, notification_flag, **kwargs): - """Returns a list of snapshots for the specified volume. + def set_volume_snapshot_notification(self, volume_id, enable): + """Enables/Disables snapshot space usage threshold warning for a given volume. :param volume_id: ID of volume. - :param kwargs: - :return: Returns a list of snapshots for the specified volume. + :param enable: Enable/Disable flag for snapshot warning notification. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. """ - return self.client.call('Network_Storage', 'setSnapshotNotification', notification_flag, id=volume_id , **kwargs) + return self.client.call('Network_Storage', 'setSnapshotNotification', enable, id=volume_id) + + def get_volume_snapshot_notification_status(self, volume_id): + """returns Enabled/Disabled status of snapshot space usage threshold warning for a given volume. + + :param volume_id: ID of volume. + :return: Enables/Disables snapshot space usage threshold warning for a given volume. + """ + return self.client.call('Network_Storage', 'getSnapshotNotificationStatus', id=volume_id) def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None, ip_address_ids=None, subnet_ids=None): From cce0849fb600447774daf7ff44f33e9b21bf7a3b Mon Sep 17 00:00:00 2001 From: cmp Date: Thu, 28 Oct 2021 23:07:48 -0500 Subject: [PATCH 0757/1582] Update API docs link and remove travisCI mention Fixes #1538 --- docs/dev/index.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/dev/index.rst b/docs/dev/index.rst index a0abdcc13..9174186cd 100644 --- a/docs/dev/index.rst +++ b/docs/dev/index.rst @@ -72,10 +72,7 @@ your code. You can run only the linting checks by using this command: The project's configuration instructs tox to test against many different versions of Python. A tox test will use as many of those as it can find on your -local computer. Rather than installing all those versions, we recommend that -you point the `Travis `_ continuous integration tool at -your GitHub fork. Travis will run the test against the full suite of Python -versions every time you push new code. +local computer. Using tox to run tests in multiple environments can be very time consuming. If you wish to quickly run the tests in your own environment, you @@ -178,7 +175,7 @@ Developer Resources ------------------- .. toctree:: - SoftLayer API Documentation + SoftLayer API Documentation Source on GitHub Issues Pull Requests From 4ccb9b823048d80a3b06124b6a0255f1ae0bf0b0 Mon Sep 17 00:00:00 2001 From: Hariharan Krishna Date: Wed, 3 Nov 2021 22:00:09 +0530 Subject: [PATCH 0758/1582] 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 0759/1582] 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 0760/1582] 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 0761/1582] 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 0762/1582] #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 0763/1582] 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 0764/1582] 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 0765/1582] 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 0766/1582] 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 0767/1582] #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 0768/1582] 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 0769/1582] 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 0770/1582] 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 0771/1582] 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 0772/1582] 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 0773/1582] 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 0774/1582] 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 0775/1582] 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 0776/1582] #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 0777/1582] #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 0778/1582] 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 0779/1582] #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 0780/1582] 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 0781/1582] 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 0782/1582] 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 0783/1582] 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 0784/1582] 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 0785/1582] 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 0786/1582] 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 0787/1582] 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 0788/1582] 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 0789/1582] 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 0790/1582] #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 0791/1582] #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 0792/1582] 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 0793/1582] 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 0794/1582] 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 0795/1582] 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 0796/1582] 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 0797/1582] 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 0798/1582] 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 0799/1582] 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 0800/1582] 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 0801/1582] 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 0802/1582] 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 0803/1582] 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 0804/1582] 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 0805/1582] 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 0806/1582] 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 0807/1582] 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 0808/1582] 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 0809/1582] 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 0810/1582] 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 0811/1582] 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 0812/1582] 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 0813/1582] 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 0814/1582] 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 0815/1582] 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 0816/1582] 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 0817/1582] 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 0818/1582] 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 0819/1582] 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 0820/1582] #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 0821/1582] 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 0822/1582] 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 0823/1582] 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 0824/1582] 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 0825/1582] 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 0826/1582] 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 0827/1582] 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 0828/1582] #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 0829/1582] 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 0830/1582] 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 0831/1582] 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 0832/1582] 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 0833/1582] 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 0834/1582] #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 0835/1582] 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 0836/1582] 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 0837/1582] 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 0838/1582] 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 0839/1582] 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 0840/1582] 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 0841/1582] 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 0842/1582] 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 0843/1582] 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 0844/1582] 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 0845/1582] 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 0846/1582] 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 0847/1582] 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 0848/1582] 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 0849/1582] 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 0850/1582] 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