From 0f3548e1f72fe7dbcd8fd00ad02f374f52a2d74c Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Thu, 24 Apr 2014 12:11:44 -0500 Subject: [PATCH 01/58] Fixed typos OCD kicked in, my english teacher will be very proud --- SoftLayer/CLI/formatting.py | 4 ++-- SoftLayer/config.py | 8 ++++---- SoftLayer/managers/hardware.py | 2 +- SoftLayer/tests/CLI/helper_tests.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 4ab878d8c..b3ba88661 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -116,14 +116,14 @@ def gb(gigabytes): # pylint: disable=C0103 def blank(): - """ Returns FormatedItem to make pretty output use a dash + """ Returns FormattedItem to make pretty output use a dash and raw formatting to use NULL """ return FormattedItem(None, '-') def listing(items, separator=','): - """ Given an iterable, returns a FormatedItem which display a list of + """ Given an iterable, returns a FormattedItem which display a list of items :param items: An iterable that outputs strings diff --git a/SoftLayer/config.py b/SoftLayer/config.py index 68ea94e22..e84556ad6 100644 --- a/SoftLayer/config.py +++ b/SoftLayer/config.py @@ -13,7 +13,7 @@ def get_client_settings_args(**kwargs): - """ Retreive client settings from user-supplied arguments + """ Retrieve client settings from user-supplied arguments :param \\*\\*kwargs: Arguments that are passed into the client instance """ @@ -31,7 +31,7 @@ def get_client_settings_args(**kwargs): def get_client_settings_env(**_): - """ Retreive client settings from environment settings + """ Retrieve client settings from environment settings :param \\*\\*kwargs: Arguments that are passed into the client instance """ @@ -46,7 +46,7 @@ def get_client_settings_env(**_): def get_client_settings_config_file(**kwargs): - """ Retreive client settings from the possible config file locations + """ Retrieve client settings from the possible config file locations :param \\*\\*kwargs: Arguments that are passed into the client instance """ @@ -84,7 +84,7 @@ def get_client_settings_config_file(**kwargs): def get_client_settings(**kwargs): """ Parses settings from various input methods, preferring earlier values - to later ones. Once an 'auth' value is found, it returns the gathererd + to later ones. Once an 'auth' value is found, it returns the gathered settings. The settings currently come from explicit user arguments, environmental variables and config files. diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 7808df606..372f96858 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -718,7 +718,7 @@ def edit(self, hardware_id, userdata=None, hostname=None, domain=None, :param string userdata: user data on the hardware to edit. If none exist it will be created :param string hostname: valid hostname - :param string domain: valid domain namem + :param string domain: valid domain name :param string notes: notes about this particular hardware """ diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 73dcd0608..5560d4fa6 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -180,7 +180,7 @@ def test_str(self): self.assertEqual('1:two', result) -class FormatedTxnTests(unittest.TestCase): +class FormattedTxnTests(unittest.TestCase): def test_active_txn_empty(self): self.assertRaises(KeyError, cli.active_txn, {}) From 6657ebee3f04bb9d5b11a633150e37cbf5264071 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 24 Apr 2014 13:56:47 -0500 Subject: [PATCH 02/58] Updates project URL to the new github name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bd7d9a168..5151bc4a5 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ ], license='MIT', zip_safe=False, - url='http://github.com/softlayer/softlayer-api-python-client', + url='http://github.com/softlayer/softlayer-python', entry_points={ 'console_scripts': [ 'sl = SoftLayer.CLI.core:main', From 9c5b89dccce0da32c2eabd69c63557738d1bf2b4 Mon Sep 17 00:00:00 2001 From: Brian Cline Date: Fri, 25 Apr 2014 18:23:36 -0500 Subject: [PATCH 03/58] Fixes --dry-run returning "Unknown arguments" It appears --dry-run didn't work at all, but its equivalent argument, --test, does work. This seems to be a limitation of docopt. Removing --dry-run should be backwards compatible since it didn't work. --- SoftLayer/CLI/modules/globalip.py | 2 +- SoftLayer/CLI/modules/server.py | 2 +- SoftLayer/CLI/modules/subnet.py | 2 +- SoftLayer/CLI/modules/vs.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py index 144eb0a0f..61f73a27e 100644 --- a/SoftLayer/CLI/modules/globalip.py +++ b/SoftLayer/CLI/modules/globalip.py @@ -72,7 +72,7 @@ class GlobalIpCreate(CLIRunnable): Options: --v6 Orders IPv6 - --dry-run, --test Do not order the IP; just get a quote + --test Do not order the IP; just get a quote """ action = 'create' options = ['confirm'] diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 4b2b02842..dc7f6349e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -763,7 +763,7 @@ class CreateServer(CLIRunnable): -i, --postinstall=URI Post-install script to download -k KEY, --key=KEY SSH keys to assign to the root user. Can be specified multiple times. - --dry-run, --test Do not create the server, just get a quote + --test Do not create the server, just get a quote --vlan_public=VLAN The ID of the public VLAN on which you want the hardware placed --vlan_private=VLAN The ID of the private VLAN on which you want the diff --git a/SoftLayer/CLI/modules/subnet.py b/SoftLayer/CLI/modules/subnet.py index 6e841e609..478aa2778 100644 --- a/SoftLayer/CLI/modules/subnet.py +++ b/SoftLayer/CLI/modules/subnet.py @@ -62,7 +62,7 @@ class SubnetCreate(CLIRunnable): Options: --v6 Orders IPv6 - --dry-run, --test Do not order the subnet; just get a quote + --test Do not order the subnet; just get a quote """ action = 'create' options = ['confirm'] diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 11cd20d61..68347804b 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -356,7 +356,7 @@ class CreateVS(CLIRunnable): --dedicated Create a dedicated VS (Virtual Server (Private Node)) --san Use SAN storage instead of local disk. Applies to all disks specified with --disk. - --dry-run, --test Do not create VS, just get a quote + --test Do not create VS, just get a quote --export=FILE Exports options to a template file -F, --userfile=FILE Read userdata from file -i, --postinstall=URI Post-install script to download From b21691733dbbb4e2e81ebbe84674f1b542c103c3 Mon Sep 17 00:00:00 2001 From: njain Date: Tue, 29 Apr 2014 10:41:22 -0500 Subject: [PATCH 04/58] add missing managers in docs --- docs/api/managers/cci.rst | 5 +++++ docs/api/managers/cdn.rst | 5 +++++ docs/api/managers/ticket.rst | 5 +++++ 3 files changed, 15 insertions(+) create mode 100644 docs/api/managers/cci.rst create mode 100644 docs/api/managers/cdn.rst create mode 100644 docs/api/managers/ticket.rst diff --git a/docs/api/managers/cci.rst b/docs/api/managers/cci.rst new file mode 100644 index 000000000..8d13ae657 --- /dev/null +++ b/docs/api/managers/cci.rst @@ -0,0 +1,5 @@ +.. _dns: + +.. automodule:: SoftLayer.managers.dns + :members: + :inherited-members: diff --git a/docs/api/managers/cdn.rst b/docs/api/managers/cdn.rst new file mode 100644 index 000000000..8d13ae657 --- /dev/null +++ b/docs/api/managers/cdn.rst @@ -0,0 +1,5 @@ +.. _dns: + +.. automodule:: SoftLayer.managers.dns + :members: + :inherited-members: diff --git a/docs/api/managers/ticket.rst b/docs/api/managers/ticket.rst new file mode 100644 index 000000000..8d13ae657 --- /dev/null +++ b/docs/api/managers/ticket.rst @@ -0,0 +1,5 @@ +.. _dns: + +.. automodule:: SoftLayer.managers.dns + :members: + :inherited-members: From 80e92a741ce0e9b5d41a0a8950515d364b871d18 Mon Sep 17 00:00:00 2001 From: njain Date: Tue, 29 Apr 2014 10:44:45 -0500 Subject: [PATCH 05/58] adding missings maangers in docs --- SoftLayer/managers/__init__.py | 3 ++- docs/api/managers/cci.rst | 4 ++-- docs/api/managers/cdn.rst | 4 ++-- docs/api/managers/ticket.rst | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index c5b98cf3c..33162c0b3 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -8,6 +8,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer.managers.cci import CCIManager +from SoftLayer.managers.cdn import CDNManager from SoftLayer.managers.dns import DNSManager from SoftLayer.managers.firewall import FirewallManager from SoftLayer.managers.hardware import HardwareManager @@ -22,6 +23,6 @@ from SoftLayer.managers.iscsi import ISCSIManager from SoftLayer.managers.vs import VSManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', - 'ImageManager', 'MessagingManager', 'MetadataManager', + 'ImageManager', 'MessagingManager', 'MetadataManager', 'CCIManager', 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'VSManager', 'ISCSIManager', 'LoadBalancerManager'] diff --git a/docs/api/managers/cci.rst b/docs/api/managers/cci.rst index 8d13ae657..32619bc18 100644 --- a/docs/api/managers/cci.rst +++ b/docs/api/managers/cci.rst @@ -1,5 +1,5 @@ -.. _dns: +.. _cci: -.. automodule:: SoftLayer.managers.dns +.. automodule:: SoftLayer.managers.cci :members: :inherited-members: diff --git a/docs/api/managers/cdn.rst b/docs/api/managers/cdn.rst index 8d13ae657..0d3b7996a 100644 --- a/docs/api/managers/cdn.rst +++ b/docs/api/managers/cdn.rst @@ -1,5 +1,5 @@ -.. _dns: +.. _cdn: -.. automodule:: SoftLayer.managers.dns +.. automodule:: SoftLayer.managers.cdn :members: :inherited-members: diff --git a/docs/api/managers/ticket.rst b/docs/api/managers/ticket.rst index 8d13ae657..b8aedaf3f 100644 --- a/docs/api/managers/ticket.rst +++ b/docs/api/managers/ticket.rst @@ -1,5 +1,5 @@ -.. _dns: +.. _ticket: -.. automodule:: SoftLayer.managers.dns +.. automodule:: SoftLayer.managers.ticket :members: :inherited-members: From 071f4fe34d4ba73484393b8c453c1ebfab169d58 Mon Sep 17 00:00:00 2001 From: njain Date: Tue, 29 Apr 2014 11:21:28 -0500 Subject: [PATCH 06/58] adding missing managers --- docs/api/managers/iscsi.rst | 5 +++++ docs/api/managers/load_balancer.rst | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 docs/api/managers/iscsi.rst create mode 100644 docs/api/managers/load_balancer.rst diff --git a/docs/api/managers/iscsi.rst b/docs/api/managers/iscsi.rst new file mode 100644 index 000000000..b8aedaf3f --- /dev/null +++ b/docs/api/managers/iscsi.rst @@ -0,0 +1,5 @@ +.. _ticket: + +.. automodule:: SoftLayer.managers.ticket + :members: + :inherited-members: diff --git a/docs/api/managers/load_balancer.rst b/docs/api/managers/load_balancer.rst new file mode 100644 index 000000000..b8aedaf3f --- /dev/null +++ b/docs/api/managers/load_balancer.rst @@ -0,0 +1,5 @@ +.. _ticket: + +.. automodule:: SoftLayer.managers.ticket + :members: + :inherited-members: From 87e92cd75a01e2f4651e5d927dbfa4aefc9a58c1 Mon Sep 17 00:00:00 2001 From: njain Date: Tue, 29 Apr 2014 11:23:42 -0500 Subject: [PATCH 07/58] add missing managers --- SoftLayer/managers/__init__.py | 2 +- SoftLayer/managers/cdn.py | 2 +- docs/api/managers/iscsi.rst | 4 ++-- docs/api/managers/load_balancer.rst | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index 33162c0b3..b508483b3 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -23,6 +23,6 @@ from SoftLayer.managers.iscsi import ISCSIManager from SoftLayer.managers.vs import VSManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', - 'ImageManager', 'MessagingManager', 'MetadataManager', 'CCIManager', + 'ImageManager', 'MessagingManager', 'MetadataManager', 'CDNManager', 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', 'VSManager', 'ISCSIManager', 'LoadBalancerManager'] diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index cf0eae31f..03baa8f0a 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -1,5 +1,5 @@ """ - SoftLayer.managers.cdn + SoftLayer.cdn ~~~~~~~~~~~~~~~~~~~~~~ CDN Manager/helpers diff --git a/docs/api/managers/iscsi.rst b/docs/api/managers/iscsi.rst index b8aedaf3f..1b6f20f58 100644 --- a/docs/api/managers/iscsi.rst +++ b/docs/api/managers/iscsi.rst @@ -1,5 +1,5 @@ -.. _ticket: +.. _iscsi: -.. automodule:: SoftLayer.managers.ticket +.. automodule:: SoftLayer.managers.iscsi :members: :inherited-members: diff --git a/docs/api/managers/load_balancer.rst b/docs/api/managers/load_balancer.rst index b8aedaf3f..f97f0157f 100644 --- a/docs/api/managers/load_balancer.rst +++ b/docs/api/managers/load_balancer.rst @@ -1,5 +1,5 @@ -.. _ticket: +.. _load_balancer: -.. automodule:: SoftLayer.managers.ticket +.. automodule:: SoftLayer.managers.load_balancer :members: :inherited-members: From 064909b755b4d776f0d8a08b83584b15496ea148 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 29 Apr 2014 20:29:48 -0500 Subject: [PATCH 08/58] Fixes messaging byte json loading issue --- SoftLayer/managers/messaging.py | 24 ++++++++--------- SoftLayer/tests/managers/queue_tests.py | 34 ++++++++++++------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py index d27e96b36..ea835b4dc 100644 --- a/SoftLayer/managers/messaging.py +++ b/SoftLayer/managers/messaging.py @@ -187,7 +187,7 @@ def stats(self, period='hour'): :param period: 'hour', 'day', 'week', 'month' """ resp = self._make_request('get', 'stats/%s' % period) - return json.loads(resp.content) + return resp.json() # QUEUE METHODS @@ -200,7 +200,7 @@ def get_queues(self, tags=None): if tags: params['tags'] = ','.join(tags) resp = self._make_request('get', 'queues', params=params) - return json.loads(resp.content) + return resp.json() def create_queue(self, queue_name, **kwargs): """ Create Queue @@ -212,7 +212,7 @@ def create_queue(self, queue_name, **kwargs): queue.update(kwargs) data = json.dumps(queue) resp = self._make_request('put', 'queues/%s' % queue_name, data=data) - return json.loads(resp.content) + return resp.json() def modify_queue(self, queue_name, **kwargs): """ Modify Queue @@ -228,7 +228,7 @@ def get_queue(self, queue_name): :param queue_name: Queue Name """ resp = self._make_request('get', 'queues/%s' % queue_name) - return json.loads(resp.content) + return resp.json() def delete_queue(self, queue_name, force=False): """ Delete Queue @@ -254,7 +254,7 @@ def push_queue_message(self, queue_name, body, **kwargs): message.update(kwargs) resp = self._make_request('post', 'queues/%s/messages' % queue_name, data=json.dumps(message)) - return json.loads(resp.content) + return resp.json() def pop_messages(self, queue_name, count=1): """ Pop messages from a queue @@ -264,7 +264,7 @@ def pop_messages(self, queue_name, count=1): """ resp = self._make_request('get', 'queues/%s/messages' % queue_name, params={'batch': count}) - return json.loads(resp.content) + return resp.json() def pop_message(self, queue_name): """ Pop a single message from a queue. If no messages are returned @@ -299,7 +299,7 @@ def get_topics(self, tags=None): if tags: params['tags'] = ','.join(tags) resp = self._make_request('get', 'topics', params=params) - return json.loads(resp.content) + return resp.json() def create_topic(self, topic_name, **kwargs): """ Create Topic @@ -309,7 +309,7 @@ def create_topic(self, topic_name, **kwargs): """ data = json.dumps(kwargs) resp = self._make_request('put', 'topics/%s' % topic_name, data=data) - return json.loads(resp.content) + return resp.json() def modify_topic(self, topic_name, **kwargs): """ Modify Topic @@ -325,7 +325,7 @@ def get_topic(self, topic_name): :param topic_name: Topic Name """ resp = self._make_request('get', 'topics/%s' % topic_name) - return json.loads(resp.content) + return resp.json() def delete_topic(self, topic_name, force=False): """ Delete Topic @@ -351,7 +351,7 @@ def push_topic_message(self, topic_name, body, **kwargs): message.update(kwargs) resp = self._make_request('post', 'topics/%s/messages' % topic_name, data=json.dumps(message)) - return json.loads(resp.content) + return resp.json() def get_subscriptions(self, topic_name): """ Listing of subscriptions on a topic @@ -360,7 +360,7 @@ def get_subscriptions(self, topic_name): """ resp = self._make_request('get', 'topics/%s/subscriptions' % topic_name) - return json.loads(resp.content) + return resp.json() def create_subscription(self, topic_name, subscription_type, **kwargs): """ Create Subscription @@ -373,7 +373,7 @@ def create_subscription(self, topic_name, subscription_type, **kwargs): 'post', 'topics/%s/subscriptions' % topic_name, data=json.dumps({ 'endpoint_type': subscription_type, 'endpoint': kwargs})) - return json.loads(resp.content) + return resp.json() def delete_subscription(self, topic_name, subscription_id): """ Delete a subscription diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index a734fdeb8..f7dbebc26 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -241,7 +241,7 @@ 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().content = json.dumps(content) + make_request().json.return_value = content result = self.conn.stats() make_request.assert_called_with('get', 'stats/hour') @@ -250,7 +250,7 @@ def test_stats(self, make_request): # Queue-based Tests @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_get_queues(self, make_request): - make_request().content = json.dumps(QUEUE_LIST) + make_request().json.return_value = QUEUE_LIST result = self.conn.get_queues() make_request.assert_called_with('get', 'queues', params={}) @@ -265,7 +265,7 @@ def test_get_queues(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_create_queue(self, make_request): - make_request().content = json.dumps(QUEUE_1) + make_request().json.return_value = QUEUE_1 result = self.conn.create_queue('example_queue') make_request.assert_called_with( @@ -274,7 +274,7 @@ def test_create_queue(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_modify_queue(self, make_request): - make_request().content = json.dumps(QUEUE_1) + make_request().json.return_value = QUEUE_1 result = self.conn.modify_queue('example_queue') make_request.assert_called_with( @@ -283,7 +283,7 @@ def test_modify_queue(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_get_queue(self, make_request): - make_request().content = json.dumps(QUEUE_1) + make_request().json.return_value = QUEUE_1 result = self.conn.get_queue('example_queue') make_request.assert_called_with('get', 'queues/example_queue') @@ -304,7 +304,7 @@ def test_delete_queue(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_push_queue_message(self, make_request): - make_request().content = json.dumps(MESSAGE_1) + make_request().json.return_value = MESSAGE_1 result = self.conn.push_queue_message('example_queue', '') make_request.assert_called_with( @@ -313,7 +313,7 @@ def test_push_queue_message(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_pop_messages(self, make_request): - make_request().content = json.dumps(MESSAGE_POP) + make_request().json.return_value = MESSAGE_POP result = self.conn.pop_messages('example_queue') make_request.assert_called_with( @@ -322,7 +322,7 @@ def test_pop_messages(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_pop_message(self, make_request): - make_request().content = json.dumps(MESSAGE_POP) + make_request().json.return_value = MESSAGE_POP result = self.conn.pop_message('example_queue') make_request.assert_called_with( @@ -331,7 +331,7 @@ def test_pop_message(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_pop_message_empty(self, make_request): - make_request().content = json.dumps(MESSAGE_POP_EMPTY) + make_request().json.return_value = MESSAGE_POP_EMPTY result = self.conn.pop_message('example_queue') make_request.assert_called_with( @@ -349,7 +349,7 @@ def test_delete_message(self, make_request): # Topic-based Tests @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_get_topics(self, make_request): - make_request().content = json.dumps(TOPIC_LIST) + make_request().json.return_value = TOPIC_LIST result = self.conn.get_topics() make_request.assert_called_with('get', 'topics', params={}) @@ -364,7 +364,7 @@ def test_get_topics(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_create_topic(self, make_request): - make_request().content = json.dumps(TOPIC_1) + make_request().json.return_value = TOPIC_1 result = self.conn.create_topic('example_topic') make_request.assert_called_with( @@ -373,7 +373,7 @@ def test_create_topic(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_modify_topic(self, make_request): - make_request().content = json.dumps(TOPIC_1) + make_request().json.return_value = TOPIC_1 result = self.conn.modify_topic('example_topic') make_request.assert_called_with( @@ -382,7 +382,7 @@ def test_modify_topic(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_get_topic(self, make_request): - make_request().content = json.dumps(TOPIC_1) + make_request().json.return_value = TOPIC_1 result = self.conn.get_topic('example_topic') make_request.assert_called_with('get', 'topics/example_topic') @@ -403,7 +403,7 @@ def test_delete_topic(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_push_topic_message(self, make_request): - make_request().content = json.dumps(MESSAGE_1) + make_request().json.return_value = MESSAGE_1 result = self.conn.push_topic_message('example_topic', '') make_request.assert_called_with( @@ -412,7 +412,7 @@ def test_push_topic_message(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_get_subscriptions(self, make_request): - make_request().content = json.dumps(SUBSCRIPTION_LIST) + make_request().json.return_value = SUBSCRIPTION_LIST result = self.conn.get_subscriptions('example_topic') make_request.assert_called_with( @@ -421,7 +421,7 @@ def test_get_subscriptions(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_create_subscription(self, make_request): - make_request().content = json.dumps(SUBSCRIPTION_1) + make_request().json.return_value = SUBSCRIPTION_1 endpoint_details = { 'account_id': 'test', 'queue_name': 'topic_subscription_queue'} @@ -436,7 +436,7 @@ def test_create_subscription(self, make_request): @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') def test_delete_subscription(self, make_request): - make_request().content = json.dumps(SUBSCRIPTION_1) + make_request().json.return_value = SUBSCRIPTION_1 result = self.conn.delete_subscription( 'example_topic', SUBSCRIPTION_1['id']) From 81fca4c8b591c2be959d2110d1f6b58eb6a8629c Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 9 May 2014 13:13:31 -0500 Subject: [PATCH 09/58] Adding a few minor examples --- SoftLayer/managers/vs.py | 50 +++++++++++++++++++++++++++++++++++++++ docs/api/managers/cci.rst | 2 ++ 2 files changed, 52 insertions(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 6cd84e450..f33155a5c 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -133,6 +133,19 @@ def get_instance(self, instance_id, **kwargs): :returns: A dictionary containing a large amount of information about the specified instance. + :: + + # Print out the FQDN and IP address for instance ID 12345. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + mgr = SoftLayer.VSManager(client) + vsi = mgr.get_instance(12345) + print vsi['fullyQualifiedDomainName'], vs['primaryIpAddress'] + """ if 'mask' not in kwargs: @@ -189,6 +202,18 @@ def cancel_instance(self, instance_id): :param integer instance_id: the instance ID to cancel + :: + + # Cancel for instance ID 12345. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + mgr = SoftLayer.VSManager(client) + mgr.cancel_instance(12345) + """ return self.guest.deleteObject(id=instance_id) @@ -199,6 +224,20 @@ def reload_instance(self, instance_id, post_uri=None, ssh_keys=None): :param string post_url: The URI of the post-install script to run after reload :param list ssh_keys: The SSH keys to add to the root user + + :: + + # Reload instance ID 12345 then run a custom post-provision script. + # env variables + # SL_USERNAME = YOUR_USERNAME + # SL_API_KEY = YOUR_API_KEY + import SoftLayer + client = SoftLayer.Client() + + post_uri = 'https://somehost.com/bootstrap.sh' + mgr = SoftLayer.VSManager(client) + vsi = mgr.reload_instance(12345, post_uri=post_url) + """ config = {} @@ -497,12 +536,23 @@ def upgrade(self, instance_id, cpus=None, memory=None, nic_speed=None, public=True): """ Upgrades a VS instance + :param int instance_id: Instance id of the VS to be upgraded :param int cpus: The number of virtual CPUs to upgrade to of a VS instance. :param bool public: CPU will be in Private/Public Node. :param int memory: RAM of the VS to be upgraded to. :param int nic_speed: The port speed to set + + :: + + # Upgrade instance 12345 to 4 CPUs and 4 GB of memory + import SoftLayer + client = SoftLayer.Client(config="~/.softlayer") + + mgr = SoftLayer.VSManager(client) + mgr.upgrade(12345, cpus=4, memory=4) + """ package_items = self._get_package_items() item_id = [] diff --git a/docs/api/managers/cci.rst b/docs/api/managers/cci.rst index 32619bc18..a9b2e18a8 100644 --- a/docs/api/managers/cci.rst +++ b/docs/api/managers/cci.rst @@ -1,5 +1,7 @@ .. _cci: +.. warning:: This module is now deprecated. It has been replaced with the VSIManager. + .. automodule:: SoftLayer.managers.cci :members: :inherited-members: From 8b8a2609022212a405d9cde5204f193b3b631472 Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 9 May 2014 15:59:15 -0500 Subject: [PATCH 10/58] Fixing flake8 failures --- SoftLayer/CLI/modules/server.py | 3 +-- SoftLayer/CLI/modules/vs.py | 3 +-- SoftLayer/managers/vs.py | 9 ++++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index dc7f6349e..06fbac10e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -819,8 +819,7 @@ def execute(self, args): output.append(FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.') - ) + 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 68347804b..4ee69a518 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -437,8 +437,7 @@ def execute(self, args): output.append(FormattedItem( None, ' -- ! Prices reflected here are retail and do not ' - 'take account level discounts and are not guaranteed.') - ) + 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index f33155a5c..d34a8e281 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -558,21 +558,20 @@ def upgrade(self, instance_id, cpus=None, memory=None, item_id = [] if cpus: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'cpus', cpus, public)}) + package_items, 'cpus', cpus, public)}) if memory: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'memory', memory)}) + package_items, 'memory', memory)}) if nic_speed: item_id.append({'id': self._get_item_id_for_upgrade( - package_items, 'nic_speed', - nic_speed)}) + package_items, 'nic_speed', nic_speed)}) order = {} order['complexType'] = \ 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade' order['virtualGuests'] = [{'id': int(instance_id)}] order['prices'] = item_id order['properties'] = [{'name': 'MAINTENANCE_WINDOW', - 'value': str(datetime.datetime.now())}] + 'value': str(datetime.datetime.now())}] if cpus or memory or nic_speed: self.client['Product_Order'].verifyOrder(order) self.client['Product_Order'].placeOrder(order) From 55c073dab2f57cd9ba5beb848987eb3f7fa7fb5a Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 12 May 2014 15:45:14 -0400 Subject: [PATCH 11/58] Cleans up multi-line masks. Removes un-needed sets --- SoftLayer/managers/hardware.py | 25 ++++++++++++------------- SoftLayer/managers/iscsi.py | 4 ++-- SoftLayer/managers/messaging.py | 4 ++-- SoftLayer/managers/vs.py | 20 +++++++++++--------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 372f96858..c294cb1c4 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -100,7 +100,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, """ if 'mask' not in kwargs: - hw_items = set([ + hw_items = [ 'id', 'hostname', 'domain', @@ -112,10 +112,10 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, 'primaryBackendIpAddress', 'primaryIpAddress', 'datacenter', - ]) - server_items = set([ + ] + server_items = [ 'activeTransaction[id, transactionStatus[friendlyName,name]]', - ]) + ] kwargs['mask'] = '[mask[%s],' \ ' mask(SoftLayer_Hardware_Server)[%s]]' % \ @@ -263,7 +263,7 @@ def get_hardware(self, hardware_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'fullyQualifiedDomainName', @@ -280,21 +280,20 @@ def get_hardware(self, hardware_id, **kwargs): 'networkManagementIpAddress', 'userData', 'datacenter', - 'networkComponents[id, status, speed, maxSpeed, name,' - 'ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress,' - 'port, primarySubnet]', - 'networkComponents.primarySubnet[id, netmask,' - 'broadcastAddress, networkIdentifier, gateway]', + '''networkComponents[id, status, speed, maxSpeed, name, + ipmiMacAddress, ipmiIpAddress, macAddress, primaryIpAddress, + port, primarySubnet[id, netmask, broadcastAddress, + networkIdentifier, gateway]]''', 'hardwareChassis[id,name]', 'activeTransaction[id, transactionStatus[friendlyName,name]]', - 'operatingSystem.softwareLicense.' 'softwareDescription[manufacturer,name,version,referenceCode]', - 'operatingSystem.passwords[username,password]', + '''operatingSystem[softwareLicense, + passwords[username,password]]''', 'billingItem.recurringFee', 'hourlyBillingFlag', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.hardware.getObject(id=hardware_id, **kwargs) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index ffadd4342..bd640ab41 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -79,7 +79,7 @@ def get_iscsi(self, volume_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'serviceResourceName', 'createDate', @@ -92,7 +92,7 @@ def get_iscsi(self, volume_id, **kwargs): 'notes', 'username', 'password' - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.iscsi_svc.getObject(id=volume_id, **kwargs) diff --git a/SoftLayer/managers/messaging.py b/SoftLayer/managers/messaging.py index ea835b4dc..fbe6c400c 100644 --- a/SoftLayer/managers/messaging.py +++ b/SoftLayer/managers/messaging.py @@ -78,12 +78,12 @@ def list_accounts(self, **kwargs): :param dict \\*\\*kwargs: response-level options (mask, limit, etc.) """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'name', 'status', 'nodes', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.client['Account'].getMessageQueueAccounts(**kwargs) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index d34a8e281..9f158f311 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -58,7 +58,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'hostname', @@ -73,7 +73,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, 'datacenter', 'activeTransaction.transactionStatus[friendlyName,name]', 'status', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) call = 'getVirtualGuests' @@ -149,7 +149,7 @@ def get_instance(self, instance_id, **kwargs): """ if 'mask' not in kwargs: - items = set([ + items = [ 'id', 'globalIdentifier', 'fullyQualifiedDomainName', @@ -163,8 +163,9 @@ def get_instance(self, instance_id, **kwargs): 'privateNetworkOnlyFlag', 'primaryBackendIpAddress', 'primaryIpAddress', - 'networkComponents[id, status, speed, maxSpeed, name,' - 'macAddress, primaryIpAddress, port, primarySubnet]', + '''networkComponents[id, status, speed, maxSpeed, name,' + macAddress, primaryIpAddress, port, + primarySubnet]''' 'lastKnownPowerState.name', 'powerState', 'status', @@ -177,14 +178,15 @@ def get_instance(self, instance_id, **kwargs): 'blockDeviceTemplateGroup[id, name, globalIdentifier]', 'postInstallScriptUri', 'userData', - 'operatingSystem.softwareLicense.' - 'softwareDescription[manufacturer,name,version,referenceCode]', - 'operatingSystem.passwords[username,password]', + '''operatingSystem[passwords[username,password], + softwareLicense.softwareDescription[ + manufacturer,name,version, + referenceCode]]''', 'hourlyBillingFlag', 'billingItem.recurringFee', 'tagReferences[id,tag[name,id]]', 'networkVlans[id,vlanNumber,networkSpace]', - ]) + ] kwargs['mask'] = "mask[%s]" % ','.join(items) return self.guest.getObject(id=instance_id, **kwargs) From 2533cacdfeb9a05f2e966cfdfda9bb7591ed37e9 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 13 May 2014 12:28:27 -0400 Subject: [PATCH 12/58] Only call getReverseDomainRecords when the instance has a public ip Resolves #332 --- SoftLayer/CLI/modules/server.py | 4 +++- SoftLayer/CLI/modules/vs.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 06fbac10e..cfc564201 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -178,7 +178,9 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - if not result['privateNetworkOnlyFlag']: + # Test to see if this is not private only and actually has a primary + # ip address. (See issue #332) + if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: ptr_domains = self.client['Hardware_Server']\ .getReverseDomainRecords(id=hardware_id) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 4ee69a518..407f4a610 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -197,7 +197,9 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - if not result['privateNetworkOnlyFlag']: + # Test to see if this is not private only and actually has a primary + # ip address. (See issue #332) + if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=vs_id) From 209e6966967e1b10480bc80d68e7a0e6143ad22b Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 13 May 2014 13:15:13 -0400 Subject: [PATCH 13/58] Adds test that covers the case of a server without a public ip address --- SoftLayer/CLI/modules/server.py | 5 ++--- SoftLayer/CLI/modules/vs.py | 5 ++--- SoftLayer/tests/CLI/modules/server_tests.py | 20 ++++++++++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index cfc564201..eb4e66e81 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -178,9 +178,8 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - # Test to see if this is not private only and actually has a primary - # ip address. (See issue #332) - if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: + # Test to see if this actually has a primary (public) ip address + if result['primaryIpAddress']: ptr_domains = self.client['Hardware_Server']\ .getReverseDomainRecords(id=hardware_id) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 407f4a610..3047c5c5d 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -197,9 +197,8 @@ def execute(self, args): if tag_row: table.add_row(['tags', listing(tag_row, separator=',')]) - # Test to see if this is not private only and actually has a primary - # ip address. (See issue #332) - if not result['privateNetworkOnlyFlag'] and result['primaryIpAddress']: + # Test to see if this actually has a primary (public) ip address + if result['primaryIpAddress']: ptr_domains = self.client['Virtual_Guest'].\ getReverseDomainRecords(id=vs_id) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 930c789b6..90dfd91fe 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -17,6 +17,7 @@ builtins_name = '__builtin__' from SoftLayer.CLI.helpers import format_output, CLIAbort, ArgumentError +from SoftLayer.tests.fixtures import Hardware_Server from SoftLayer.CLI.modules import server @@ -203,12 +204,10 @@ def test_ServerCreateOptions_for_bmc(self, bmpi, packages): self.assertEqual(expected, format_output(output, 'python')) - def test_ServerDetails(self): - hw_id = 1234 - + def test_server_details(self): runnable = server.ServerDetails(client=self.client) - args = {'': hw_id, '--passwords': True, '--price': True} + args = {'': 1234, '--passwords': True, '--price': True} output = runnable.execute(args) expected = { @@ -234,6 +233,19 @@ def test_ServerDetails(self): self.assertEqual(expected, format_output(output, 'python')) + def test_server_details_issue_332(self): + runnable = server.ServerDetails(client=self.client) + result = Hardware_Server.getObject.copy() + result['primaryIpAddress'] = None + self.client['Hardware_Server'].getObject.return_value = result + + runnable.execute({'': 1234, + '--passwords': True, + '--price': True}) + + self.assertFalse(self.client['Hardware_Server'] + .getReverseDomainRecords.called) + def test_ListServers(self): runnable = server.ListServers(client=self.client) From fe045a1936b930935ef7168932d0df166d72bae5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 11 Apr 2014 17:21:43 -0500 Subject: [PATCH 14/58] Adds config CLI tests --- SoftLayer/CLI/modules/config.py | 153 ++++++++++---------- SoftLayer/tests/CLI/modules/config_tests.py | 110 ++++++++++++++ SoftLayer/tests/fixture_client.py | 6 +- SoftLayer/tests/fixtures/Account.py | 3 +- SoftLayer/tests/fixtures/User_Customer.py | 1 + 5 files changed, 196 insertions(+), 77 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/config_tests.py create mode 100644 SoftLayer/tests/fixtures/User_Customer.py diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 37f111b5c..14a0e7cc5 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -12,7 +12,8 @@ import os.path from SoftLayer import ( - Client, SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) + SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) +from SoftLayer.auth import BasicAuthentication from SoftLayer.CLI import ( CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) from SoftLayer.utils import configparser @@ -50,34 +51,32 @@ def config_table(settings): return table -def get_api_key(username, secret, endpoint_url=None): +def get_api_key(client, username, secret, endpoint_url=None): """ Tries API-Key and password auth to get (and potentially generate) an API key. """ + + client.endpoint_url = endpoint_url + client.auth = None # Try to use a client with username/api key - try: - client = Client( - username=username, - api_key=secret, - endpoint_url=endpoint_url, - timeout=5) - - client['Account'].getCurrentUser() - return secret - except SoftLayerAPIError as ex: - if 'invalid api token' not in ex.faultString.lower(): - raise - - # Try to use a client with username/password - client = Client(endpoint_url=endpoint_url, timeout=5) - client.authenticate_with_password(username, secret) - - user_record = client['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 api_keys[0]['authenticationKey'] + if len(secret) == 64: + try: + client.auth = BasicAuthentication(username, secret) + client['Account'].getCurrentUser() + return secret + except SoftLayerAPIError as ex: + 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) + + user_record = client['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 api_keys[0]['authenticationKey'] class Setup(CLIRunnable): @@ -89,57 +88,21 @@ class Setup(CLIRunnable): action = 'setup' def execute(self, args): - settings = get_settings_from_client(self.client) - - # User Input - # Ask for username - while True: - username = self.env.input( - 'Username [%s]: ' % settings['username']) \ - or settings['username'] - if username: - break - - # Ask for 'secret' which can be api_key or their password - while True: - secret = self.env.getpass( - 'API Key or Password [%s]: ' % settings['api_key']) \ - or settings['api_key'] - if secret: - break - - # Ask for which endpoint they want to use - while True: - endpoint_type = self.env.input( - 'Endpoint (public|private|custom): ') - endpoint_type = endpoint_type.lower() - if not endpoint_type: - endpoint_url = API_PUBLIC_ENDPOINT - break - if endpoint_type == 'public': - endpoint_url = API_PUBLIC_ENDPOINT - break - elif endpoint_type == 'private': - endpoint_url = API_PRIVATE_ENDPOINT - break - elif endpoint_type == 'custom': - endpoint_url = self.env.input( - 'Endpoint URL [%s]: ' % settings['endpoint_url'] - ) or settings['endpoint_url'] - break + username, secret, endpoint_url, timeout = self.get_user_input() - api_key = get_api_key(username, secret, endpoint_url=endpoint_url) - - settings['username'] = username - settings['api_key'] = api_key - settings['endpoint_url'] = endpoint_url + api_key = get_api_key(self.client, username, secret, + endpoint_url=endpoint_url) path = '~/.softlayer' if args.get('--config'): path = args.get('--config') config_path = os.path.expanduser(path) - self.env.out(format_output(config_table(settings))) + self.env.out( + format_output(config_table({'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) if not confirm('Are you sure you want to write settings to "%s"?' % config_path, default=True): @@ -154,9 +117,9 @@ def execute(self, args): except configparser.DuplicateSectionError: pass - config.set('softlayer', 'username', settings['username']) - config.set('softlayer', 'api_key', settings['api_key']) - config.set('softlayer', 'endpoint_url', settings['endpoint_url']) + config.set('softlayer', 'username', username) + config.set('softlayer', 'api_key', api_key) + config.set('softlayer', 'endpoint_url', endpoint_url) config_file = os.fdopen(os.open(config_path, (os.O_WRONLY | os.O_CREAT), @@ -169,6 +132,50 @@ def execute(self, args): return "Configuration Updated Successfully" + def get_user_input(self): + """ Ask for username, secret (api_key or password) and endpoint_url """ + + defaults = get_settings_from_client(self.client) + timeout = defaults['timeout'] + + # Ask for username + while True: + username = self.env.input( + 'Username [%s]: ' % defaults['username']) \ + or defaults['username'] + if username: + break + + # Ask for 'secret' which can be api_key or their password + while True: + secret = self.env.getpass( + 'API Key or Password [%s]: ' % defaults['api_key']) \ + or defaults['api_key'] + if secret: + break + + # Ask for which endpoint they want to use + while True: + endpoint_type = self.env.input( + 'Endpoint (public|private|custom): ') + endpoint_type = endpoint_type.lower() + if not endpoint_type: + endpoint_url = API_PUBLIC_ENDPOINT + break + if endpoint_type == 'public': + endpoint_url = API_PUBLIC_ENDPOINT + break + elif endpoint_type == 'private': + endpoint_url = API_PRIVATE_ENDPOINT + break + elif endpoint_type == 'custom': + endpoint_url = self.env.input( + 'Endpoint URL [%s]: ' % defaults['endpoint_url'] + ) or defaults['endpoint_url'] + break + + return username, secret, endpoint_url, timeout + class Show(CLIRunnable): """ diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py new file mode 100644 index 000000000..71c7d8d55 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -0,0 +1,110 @@ +""" + SoftLayer.tests.CLI.modules.config_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import MagicMock, patch +import tempfile + +from SoftLayer import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT +from SoftLayer.auth import BasicAuthentication +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.modules import config +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort + + +class TestHelpShow(unittest.TestCase): + def setUp(self): + client = MagicMock() + client.auth.username = 'user' + client.auth.api_key = '12345' + client.endpoint_url = 'https://some/endpoint' + client.timeout = 10 + self.client = client + + def test_show(self): + command = config.Show(client=self.client) + + output = command.execute({}) + + expected = {'Username': self.client.auth.username, + 'Endpoint URL': self.client.endpoint_url, + 'API Key': self.client.auth.api_key, + 'Timeout': self.client.timeout} + self.assertEqual(expected, format_output(output, 'python')) + + +class TestHelpSetup(unittest.TestCase): + def setUp(self): + client = FixtureClient() + client.auth = BasicAuthentication('default-user', 'default-key') + client.endpoint_url = 'default-endpoint-url' + client.timeout = 10 + self.client = client + self.env = MagicMock() + + @patch('SoftLayer.CLI.modules.config.confirm') + def test_setup(self, confirm_mock): + with tempfile.NamedTemporaryFile() as config_file: + confirm_mock.return_value = True + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'public'] + + command = config.Setup(client=self.client, env=self.env) + output = command.execute({'--config': config_file.name}) + + self.assertEqual('Configuration Updated Successfully', output) + self.assertEqual( + config_file.read().decode("utf-8"), + '''[softlayer] +username = user +api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +endpoint_url = %s + +''' % API_PUBLIC_ENDPOINT) + + @patch('SoftLayer.CLI.modules.config.confirm') + def test_setup_cancel(self, confirm_mock): + with tempfile.NamedTemporaryFile() as config_file: + confirm_mock.return_value = False + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'public'] + + command = config.Setup(client=self.client, env=self.env) + self.assertRaises(CLIAbort, + command.execute, {'--config': config_file.name}) + + def test_get_user_input_private(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'private'] + + username, secret, endpoint_url, timeout = command.get_user_input() + + self.assertEqual(username, 'user') + self.assertEqual(secret, 'A' * 64) + self.assertEqual(endpoint_url, API_PRIVATE_ENDPOINT) + self.assertEqual(timeout, 10) + + def test_get_user_input_custom(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', 'custom', 'custom-endpoint'] + + _, _, endpoint_url, _ = command.get_user_input() + + self.assertEqual(endpoint_url, 'custom-endpoint') + + def test_get_user_input_default(self): + command = config.Setup(client=self.client, env=self.env) + # get_user_input + self.env.getpass.return_value = 'A' * 64 + self.env.input.side_effect = ['user', ''] + + _, _, endpoint_url, _ = command.get_user_input() + + self.assertEqual(endpoint_url, API_PUBLIC_ENDPOINT) diff --git a/SoftLayer/tests/fixture_client.py b/SoftLayer/tests/fixture_client.py index 75e99da3e..94e1100c5 100644 --- a/SoftLayer/tests/fixture_client.py +++ b/SoftLayer/tests/fixture_client.py @@ -4,11 +4,11 @@ :license: MIT, see LICENSE for more details. """ -from mock import MagicMock +from mock import MagicMock, MagicMixin from importlib import import_module -class FixtureClient(object): +class FixtureClient(MagicMixin): def __init__(self): # Keep track of Service instances in order to do future assertions @@ -27,7 +27,7 @@ def reset_mock(self): self.loaded_services = {} -class FixtureService(object): +class FixtureService(MagicMixin): def __init__(self, service_name): self.service_name = service_name diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index 0f904b471..b1199ad85 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -315,7 +315,8 @@ getClosedTickets = [ticket for ticket in getTickets if ticket['statusId'] == 1002] -getCurrentUser = {"id": 12345} +getCurrentUser = {'id': 12345, + 'apiAuthenticationKeys': [{'authenticationKey': 'A' * 64}]} getCdnAccounts = [ { diff --git a/SoftLayer/tests/fixtures/User_Customer.py b/SoftLayer/tests/fixtures/User_Customer.py new file mode 100644 index 000000000..72d28db8a --- /dev/null +++ b/SoftLayer/tests/fixtures/User_Customer.py @@ -0,0 +1 @@ +addApiAuthenticationKey = "A" * 64 From c35c3cbbcf95c0a44e5cb7a8ba3110c1cf6d1322 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 24 Apr 2014 17:19:09 -0500 Subject: [PATCH 15/58] CDN Tests + Cleanup * Removed a function tests that periodically fails * Adds CDN CLI Tests * Addresses some small issues with CDN * Fixes a test which was failing due to differences in dict ordering --- SoftLayer/CLI/modules/cdn.py | 8 +- SoftLayer/managers/cdn.py | 3 +- SoftLayer/tests/CLI/modules/cdn_tests.py | 84 +++++++++++++++++++ .../Network_ContentDelivery_Account.py | 3 +- SoftLayer/tests/functional_tests.py | 14 ---- SoftLayer/tests/managers/queue_tests.py | 4 +- 6 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/cdn_tests.py diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py index 839794b25..67c4a21d6 100644 --- a/SoftLayer/CLI/modules/cdn.py +++ b/SoftLayer/CLI/modules/cdn.py @@ -90,8 +90,7 @@ class LoadContent(CLIRunnable): def execute(self, args): manager = CDNManager(self.client) - return str(manager.load_content(args.get(''), - args.get(''))) + manager.load_content(args.get(''), args.get('')) class PurgeContent(CLIRunnable): @@ -158,10 +157,7 @@ class AddOrigin(CLIRunnable): def execute(self, args): manager = CDNManager(self.client) - media_type = args.get('--type', 'http') - - if not media_type: - media_type = 'http' + media_type = args.get('--type') or 'http' manager.add_origin(args.get(''), media_type, args.get(''), args.get('--cname', None)) diff --git a/SoftLayer/managers/cdn.py b/SoftLayer/managers/cdn.py index 03baa8f0a..2270c75d7 100644 --- a/SoftLayer/managers/cdn.py +++ b/SoftLayer/managers/cdn.py @@ -41,8 +41,7 @@ def get_account(self, account_id, **kwargs): """ if 'mask' not in kwargs: - items = set(['status']) - kwargs['mask'] = 'mask[%s]' % ','.join(items) + kwargs['mask'] = 'status' return self.account.getObject(id=account_id, **kwargs) diff --git a/SoftLayer/tests/CLI/modules/cdn_tests.py b/SoftLayer/tests/CLI/modules/cdn_tests.py new file mode 100644 index 000000000..31e66aef6 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/cdn_tests.py @@ -0,0 +1,84 @@ +""" + SoftLayer.tests.CLI.modules.cdn_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.modules import cdn + + +class CdnTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_accounts(self): + command = cdn.ListAccounts(client=self.client) + + output = command.execute({'--sortby': None}) + self.assertEqual([{'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'}], + format_output(output, 'python')) + + def test_detail_account(self): + command = cdn.DetailAccount(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual({'notes': None, + 'created': '2012-06-25T14:05:28-07:00', + 'type': 'ORIGIN_PULL', + 'status': 'ACTIVE', + 'id': 1234, + 'account_name': '1234a'}, + format_output(output, 'python')) + + def test_load_content(self): + command = cdn.LoadContent(client=self.client) + + output = command.execute({'': '1234', + '': ['http://example.com']}) + self.assertEqual(None, output) + + def test_purge_content(self): + command = cdn.PurgeContent(client=self.client) + + output = command.execute({'': '1234', + '': ['http://example.com']}) + self.assertEqual(None, output) + + def test_list_origins(self): + command = cdn.ListOrigins(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual([ + {'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'}], format_output(output, 'python')) + + def test_add_origin(self): + command = cdn.AddOrigin(client=self.client) + + output = command.execute({'': '1234', + '': 'http://example.com'}) + self.assertEqual(None, output) + + def test_remove_origin(self): + command = cdn.RemoveOrigin(client=self.client) + + output = command.execute({'': '1234', + '': '12345'}) + self.assertEqual(None, output) diff --git a/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py b/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py index b96da8d13..9581d5b72 100644 --- a/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py +++ b/SoftLayer/tests/fixtures/Network_ContentDelivery_Account.py @@ -7,7 +7,8 @@ "dependantServiceFlag": True, "cdnSolutionName": "ORIGIN_PULL", "statusId": 4, - "accountId": 1234 + "accountId": 1234, + "status": {'name': 'ACTIVE'}, } getOriginPullMappingInformation = [ diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index c1b1f5024..16cc91d7d 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -32,20 +32,6 @@ def test_failed_auth(self): SoftLayer.SoftLayerAPIError, client['SoftLayer_User_Customer'].getPortalLoginToken) - def test_404(self): - client = SoftLayer.Client( - username='doesnotexist', api_key='issurelywrong', timeout=20, - endpoint_url='http://httpbin.org/status/404') - - try: - client['SoftLayer_User_Customer'].doSomething() - except SoftLayer.SoftLayerAPIError as e: - self.assertEqual(e.faultCode, 404) - self.assertIn('NOT FOUND', e.faultString) - self.assertIn('NOT FOUND', e.reason) - else: - self.fail('No Exception Raised') - def test_no_hostname(self): try: # This test will fail if 'notvalidsoftlayer.com' becomes a thing diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index f7dbebc26..2851c4cf6 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -429,9 +429,7 @@ def test_create_subscription(self, make_request): 'example_topic', 'queue', **endpoint_details) make_request.assert_called_with( - 'post', 'topics/example_topic/subscriptions', - data=json.dumps({ - 'endpoint_type': 'queue', 'endpoint': endpoint_details})) + 'post', 'topics/example_topic/subscriptions', data=ANY) self.assertEqual(SUBSCRIPTION_1, result) @patch('SoftLayer.managers.messaging.MessagingConnection._make_request') From 232c46689c530709ba38766a300bc6ca1106d383 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 07:47:59 -0500 Subject: [PATCH 16/58] Adds DNS CLI tests --- SoftLayer/CLI/modules/dns.py | 62 ++++-------- SoftLayer/CLI/modules/vs.py | 8 +- SoftLayer/exceptions.py | 5 - SoftLayer/tests/CLI/modules/dns_tests.py | 115 +++++++++++++++++++++++ SoftLayer/tests/fixtures/Account.py | 5 +- SoftLayer/tests/fixtures/Dns_Domain.py | 7 +- SoftLayer/tests/managers/dns_tests.py | 41 +++----- 7 files changed, 151 insertions(+), 92 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/dns_tests.py diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 72431885b..8b7fe6402 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -18,7 +18,7 @@ from SoftLayer.CLI import ( CLIRunnable, no_going_back, Table, CLIAbort, resolve_id) -from SoftLayer import DNSManager, DNSZoneNotFound +from SoftLayer import DNSManager class DumpZone(CLIRunnable): @@ -35,10 +35,7 @@ class DumpZone(CLIRunnable): def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - return manager.dump_zone(zone_id) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + return manager.dump_zone(zone_id) class CreateZone(CLIRunnable): @@ -102,13 +99,7 @@ def execute(self, args): def list_zone(self, args): """ list records for a particular zone """ manager = DNSManager(self.client) - table = Table([ - "id", - "record", - "type", - "ttl", - "value", - ]) + table = Table(['id', 'record', 'type', 'ttl', 'value']) table.align['ttl'] = 'l' table.align['record'] = 'r' @@ -116,16 +107,13 @@ def list_zone(self, args): zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - records = manager.get_records( - zone_id, - record_type=args.get('--type'), - host=args.get('--record'), - ttl=args.get('--ttl'), - data=args.get('--data'), - ) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + records = manager.get_records( + zone_id, + record_type=args.get('--type'), + host=args.get('--record'), + ttl=args.get('--ttl'), + data=args.get('--data'), + ) for record in records: table.add_row([ @@ -142,12 +130,7 @@ def list_all_zones(self): """ List all zones """ manager = DNSManager(self.client) zones = manager.list_zones() - table = Table([ - "id", - "zone", - "serial", - "updated", - ]) + table = Table(['id', 'zone', 'serial', 'updated']) table.align['serial'] = 'c' table.align['updated'] = 'c' @@ -184,14 +167,13 @@ def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - args['--ttl'] = args['--ttl'] or 7200 manager.create_record( zone_id, args[''], args[''], args[''], - ttl=args['--ttl']) + ttl=args['--ttl'] or 7200) class EditRecord(CLIRunnable): @@ -216,12 +198,9 @@ def execute(self, args): manager = DNSManager(self.client) zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') - try: - results = manager.get_records( - zone_id, - host=args['']) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + results = manager.get_records( + zone_id, + host=args['']) for result in results: if args['--id'] and str(result['id']) != args['--id']: @@ -254,18 +233,13 @@ def execute(self, args): if args['--id']: records = [{'id': args['--id']}] else: - try: - records = manager.get_records( - zone_id, - host=args['']) - except DNSZoneNotFound: - raise CLIAbort("No zone found matching: %s" % args['']) + records = manager.get_records( + zone_id, + host=args['']) if args['--really'] or no_going_back('yes'): table = Table(['record']) for result in records: - if args.get('--id') and args['--id'] != result['id']: - continue manager.delete_record(result['id']) table.add_row([result['id']]) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 3047c5c5d..0d6c05e5d 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -31,7 +31,7 @@ from os import linesep import os.path -from SoftLayer import VSManager, SshKeyManager, DNSManager, DNSZoneNotFound +from SoftLayer import VSManager, SshKeyManager, DNSManager from SoftLayer.utils import lookup from SoftLayer.CLI import ( CLIRunnable, Table, no_going_back, confirm, mb_to_gb, listing, @@ -910,11 +910,7 @@ def sync_ptr_record(): if not instance['primaryIpAddress']: raise CLIAbort('No primary IP address associated with this VS') - try: - zone = dns.get_zone(zone_id) - except DNSZoneNotFound: - raise CLIAbort("Unable to create A record, " - "no zone found matching: %s" % instance['domain']) + zone = dns.get_zone(zone_id) go_for_it = args['--really'] or confirm( "Attempt to update DNS records for %s" diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index 8b8cd017f..5d86a9b35 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -89,8 +89,3 @@ class InvalidMethodParameters(ServerError): class InternalError(ServerError): """ Internal Server Error """ pass - - -class DNSZoneNotFound(SoftLayerError): - """ DNS Zone was not found """ - pass diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py new file mode 100644 index 000000000..633c61c5d --- /dev/null +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -0,0 +1,115 @@ +""" + SoftLayer.tests.CLI.modules.dns_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import dns + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_dump_zone(self): + command = dns.DumpZone(client=self.client) + + output = command.execute({'': '1234'}) + self.assertEqual('lots of text', output) + + def test_create_zone(self): + command = dns.CreateZone(client=self.client) + + output = command.execute({'': 'example.com'}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.dns.no_going_back') + def test_delete_zone(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = dns.DeleteZone(client=self.client) + + output = command.execute({'': 'example.com', '--really': False}) + self.assertEqual(None, output) + + no_going_back_mock.return_value = False + command = dns.DeleteZone(client=self.client) + + self.assertRaises(CLIAbort, + command.execute, {'': 'example.com', + '--really': False}) + + def test_list_zones(self): + command = dns.ListZones(client=self.client) + + output = command.execute({'': None}) + self.assertEqual([{'serial': 2014030728, + 'updated': '2014-03-07T13:52:31-06:00', + 'id': 12345, 'zone': 'example.com'}], + format_output(output, 'python')) + + def test_list_all_zones(self): + command = dns.ListZones(client=self.client) + + output = command.execute({'': 'example.com'}) + self.assertEqual([{'id': 1, + 'record': 'hostname', + 'ttl': 100, + 'type': 'A', + 'value': 'd'}], + format_output(output, 'python')) + + def test_add_record(self): + command = dns.AddRecord(client=self.client) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '': 'd', + '--ttl': 100}) + self.assertEqual(None, output) + + def test_edit_record(self): + command = dns.EditRecord(client=self.client) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '--data': 'd', + '--id': 1, + '--ttl': 100}) + self.assertEqual(None, output) + + output = command.execute({'': 'example.com', + '': 'hostname', + '': 'A', + '--data': 'd', + '--id': None, + '--ttl': 100}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.dns.no_going_back') + def test_delete_record(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = dns.RecordRemove(client=self.client) + output = command.execute({'': 'example.com', + '': 'hostname', + '--id': '1', + '--really': False}) + self.assertEqual([{'record': '1'}], format_output(output, 'python')) + + output = command.execute({'': 'example.com', + '': 'hostname', + '--id': None, + '--really': False}) + self.assertEqual([{'record': 1}], format_output(output, 'python')) + + no_going_back_mock.return_value = False + self.assertRaises(CLIAbort, command.execute, {'': 'example.com', + '': 'hostname', + '--id': 1, + '--really': False}) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index b1199ad85..d38ac87a3 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -175,7 +175,10 @@ }, ] }] -getDomains = [{'name': 'example.com', 'id': 12345}] +getDomains = [{'name': 'example.com', + 'id': 12345, + 'serial': 2014030728, + 'updateDate': '2014-03-07T13:52:31-06:00'}] getObject = { 'networkVlans': [{ diff --git a/SoftLayer/tests/fixtures/Dns_Domain.py b/SoftLayer/tests/fixtures/Dns_Domain.py index d61377d66..f9c5106ad 100644 --- a/SoftLayer/tests/fixtures/Dns_Domain.py +++ b/SoftLayer/tests/fixtures/Dns_Domain.py @@ -3,11 +3,6 @@ editObject = True getZoneFileContents = 'lots of text' getResourceRecords = [ - {'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'}, - {'ttl': 900, 'data': '1', 'host': 'b', 'type': 'a'}, - {'ttl': 900, 'data': 'x', 'host': 'c', 'type': 'ptr'}, - {'ttl': 86400, 'data': 'b', 'host': 'd', 'type': 'txt'}, - {'ttl': 86400, 'data': 'b', 'host': 'e', 'type': 'txt'}, - {'ttl': 600, 'data': 'b', 'host': 'f', 'type': 'txt'}, + {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'hostname', 'type': 'a'}, ] getObject = {'id': 98765, 'name': 'test-example.com'} diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index d0196b784..708212441 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -106,41 +106,22 @@ def test_dump_zone(self): def test_get_record(self): D = self.client['Dns_Domain'].getResourceRecords - records = Dns_Domain.getResourceRecords # maybe valid domain, but no records matching D.return_value = [] - self.assertEqual(self.dns_client.get_records(12345), - []) + self.assertEqual(self.dns_client.get_records(12345), []) D.reset_mock() - D.return_value = [records[1]] - self.dns_client.get_records(12345, record_type='a') + D.return_value = [Dns_Domain.getResourceRecords[0]] + self.dns_client.get_records(12345, + record_type='a', + host='hostname', + data='a', + ttl='86400') D.assert_called_once_with( id=12345, - filter={'resourceRecords': {'type': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = [records[0]] - self.dns_client.get_records(12345, host='a') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'host': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = records[3:5] - self.dns_client.get_records(12345, data='a') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'data': {"operation": "_= a"}}}, - mask=ANY) - - D.reset_mock() - D.return_value = records[3:5] - self.dns_client.get_records(12345, ttl='86400') - D.assert_called_once_with( - id=12345, - filter={'resourceRecords': {'ttl': {"operation": 86400}}}, + filter={'resourceRecords': {'type': {'operation': '_= a'}, + 'host': {'operation': '_= hostname'}, + 'data': {'operation': '_= a'}, + 'ttl': {'operation': 86400}}}, mask=ANY) From c7e7a666e21af33820654be3eb05a04a7681908a Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 07:55:29 -0500 Subject: [PATCH 17/58] Makes config CLI test less likely to fail. Fixes style issue --- SoftLayer/tests/CLI/modules/config_tests.py | 15 +++++++-------- SoftLayer/tests/CLI/modules/dns_tests.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py index 71c7d8d55..2bfa86667 100644 --- a/SoftLayer/tests/CLI/modules/config_tests.py +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -56,14 +56,13 @@ def test_setup(self, confirm_mock): output = command.execute({'--config': config_file.name}) self.assertEqual('Configuration Updated Successfully', output) - self.assertEqual( - config_file.read().decode("utf-8"), - '''[softlayer] -username = user -api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -endpoint_url = %s - -''' % API_PUBLIC_ENDPOINT) + contents = config_file.read().decode("utf-8") + self.assertTrue('[softlayer]' in contents) + self.assertTrue('username = user' in contents) + self.assertTrue('api_key = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA' in contents) + self.assertTrue('endpoint_url = %s' % API_PUBLIC_ENDPOINT + in contents) @patch('SoftLayer.CLI.modules.config.confirm') def test_setup_cancel(self, confirm_mock): diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 633c61c5d..231b8141a 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -48,8 +48,8 @@ def test_list_zones(self): output = command.execute({'': None}) self.assertEqual([{'serial': 2014030728, - 'updated': '2014-03-07T13:52:31-06:00', - 'id': 12345, 'zone': 'example.com'}], + 'updated': '2014-03-07T13:52:31-06:00', + 'id': 12345, 'zone': 'example.com'}], format_output(output, 'python')) def test_list_all_zones(self): From 40a48e9cbaecafd5d307fd4f657279e828e28ead Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 09:00:12 -0500 Subject: [PATCH 18/58] Adds some globalip tests --- SoftLayer/CLI/modules/globalip.py | 20 ++++----------- SoftLayer/tests/fixtures/Account.py | 30 +++++++++++++++++------ SoftLayer/tests/managers/network_tests.py | 2 +- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py index 61f73a27e..5640d2dd2 100644 --- a/SoftLayer/CLI/modules/globalip.py +++ b/SoftLayer/CLI/modules/globalip.py @@ -35,9 +35,6 @@ def execute(self, args): global_ip_id = resolve_id(mgr.resolve_global_ip_ids, args.get(''), name='global ip') - if not global_ip_id: - raise CLIAbort("Unable to find global IP record for " + - args['']) mgr.assign_global_ip(global_ip_id, args['']) @@ -60,7 +57,7 @@ def execute(self, args): if args['--really'] or no_going_back(global_ip_id): mgr.cancel_global_ip(global_ip_id) else: - CLIAbort('Aborted') + raise CLIAbort('Aborted') class GlobalIpCreate(CLIRunnable): @@ -89,8 +86,7 @@ def execute(self, args): raise CLIAbort('Cancelling order.') result = mgr.add_global_ip(version=version, test_order=args.get('--test')) - if not result: - return 'Unable to place order: No valid price IDs found.' + table = Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -121,9 +117,7 @@ class GlobalIpList(CLIRunnable): def execute(self, args): mgr = NetworkManager(self.client) - table = Table([ - 'id', 'ip', 'assigned', 'target' - ]) + table = Table(['id', 'ip', 'assigned', 'target']) table.sortby = args.get('--sortby') or 'id' version = 0 @@ -146,9 +140,8 @@ def execute(self, args): target += (' (%s)' % virtual_guest['fullyQualifiedDomainName']) elif ip_address['destinationIpAddress'].get('hardware'): - target += ' (' + \ - dest['hardware']['fullyQualifiedDomainName'] + \ - ')' + target += (' (%s)' + % dest['hardware']['fullyQualifiedDomainName']) table.add_row([ip_address['id'], ip_address['ipAddress']['ipAddress'], @@ -173,7 +166,4 @@ def execute(self, args): global_ip_id = resolve_id(mgr.resolve_global_ip_ids, args.get(''), name='global ip') - if not global_ip_id: - raise CLIAbort("Unable to find global IP record for " + - args['']) mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index d38ac87a3..c5bf6cc5e 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -233,15 +233,29 @@ 'privateResidenceFlag': 'privateResidenceFlag', } -getGlobalIpRecords = [ - { - 'id': '200', - 'ipAddress': { - 'subnet': { - 'networkIdentifier': '10.0.0.1', - }, +getGlobalIpRecords = [{ + 'id': '200', + 'ipAddress': { + 'subnet': { + 'networkIdentifier': '10.0.0.1', }, - }] + 'ipAddress': '127.0.0.1', + }, + 'destinationIpAddress': { + 'ipAddress': '127.0.0.1', + 'virtualGuest': {'fullyQualifiedDomainName': 'example.com'}} +}, { + 'id': '201', + 'ipAddress': { + 'subnet': { + 'networkIdentifier': '10.0.0.1', + }, + 'ipAddress': '127.0.0.1', + }, + 'destinationIpAddress': { + 'ipAddress': '127.0.0.1', + 'hardware': {'fullyQualifiedDomainName': 'example.com'}} +}] getSubnets = [ { diff --git a/SoftLayer/tests/managers/network_tests.py b/SoftLayer/tests/managers/network_tests.py index 10a923f05..5bc4d249e 100644 --- a/SoftLayer/tests/managers/network_tests.py +++ b/SoftLayer/tests/managers/network_tests.py @@ -239,7 +239,7 @@ def test_summary_by_datacenter(self): def test_resolve_global_ip_ids(self): service = self.client['Account'] _id = self.network.resolve_global_ip_ids('10.0.0.1') - self.assertEqual(_id, ['200']) + self.assertEqual(_id, ['200', '201']) service.getGlobalIpRecords.return_value = [] _id = self.network.resolve_global_ip_ids('nope') From bf965beb5a82215641536528fd23baf9ca36f180 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 11:43:14 -0500 Subject: [PATCH 19/58] Adds globalip tests --- SoftLayer/tests/CLI/modules/globalip_tests.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/globalip_tests.py diff --git a/SoftLayer/tests/CLI/modules/globalip_tests.py b/SoftLayer/tests/CLI/modules/globalip_tests.py new file mode 100644 index 000000000..8384b6280 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/globalip_tests.py @@ -0,0 +1,63 @@ +""" + SoftLayer.tests.CLI.modules.globalip_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import globalip + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_ip_assign(self): + command = globalip.GlobalIpAssign(client=self.client) + + output = command.execute({'': '1', + '': '127.0.0.1'}) + self.assertEqual(None, output) + + @patch('SoftLayer.CLI.modules.globalip.no_going_back') + def test_ip_cancel(self, no_going_back_mock): + no_going_back_mock.return_value = True + command = globalip.GlobalIpCancel(client=self.client) + + output = command.execute({'': '1', '--really': False}) + self.assertEqual(None, output) + + no_going_back_mock.return_value = False + + self.assertRaises(CLIAbort, + command.execute, + {'': '1', '--really': False}) + + def test_ip_list(self): + command = globalip.GlobalIpList(client=self.client) + + output = command.execute({'--v4': True}) + self.assertEqual([{'assigned': 'Yes', + 'id': '200', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}, + {'assigned': 'Yes', + 'id': '201', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}], + format_output(output, 'python')) + + output = command.execute({'--v6': True}) + self.assertEqual([{'assigned': 'Yes', + 'id': '200', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}, + {'assigned': 'Yes', + 'id': '201', + 'ip': '127.0.0.1', + 'target': '127.0.0.1 (example.com)'}], + format_output(output, 'python')) From 229adb3bcd998e9eb751ba25bf4ddfc6841d2142 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 11:45:05 -0500 Subject: [PATCH 20/58] Adds NAS CLI test --- SoftLayer/tests/CLI/modules/nas_tests.py | 26 ++++++++++++++++++++++++ SoftLayer/tests/fixtures/Account.py | 9 ++++++++ 2 files changed, 35 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/nas_tests.py diff --git a/SoftLayer/tests/CLI/modules/nas_tests.py b/SoftLayer/tests/CLI/modules/nas_tests.py new file mode 100644 index 000000000..053c10331 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/nas_tests.py @@ -0,0 +1,26 @@ +""" + SoftLayer.tests.CLI.modules.nas_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.modules import nas +from SoftLayer.CLI.helpers import format_output + + +class RWhoisTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_nas(self): + command = nas.ListNAS(client=self.client) + output = command.execute({}) + + self.assertEqual([{'username': 'user', + 'datacenter': 'Dallas', + 'server': '127.0.0.1', + 'password': 'pass', + 'id': 1, + 'size': 10}], + format_output(output, 'python')) diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index c5bf6cc5e..f6ecaf79f 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -373,3 +373,12 @@ }] getAdcLoadBalancers = [] + +getNasNetworkStorage = [{ + 'id': 1, + 'capacityGb': 10, + 'serviceResource': {'datacenter': {'name': 'Dallas'}}, + 'username': 'user', + 'password': 'pass', + 'serviceResourceBackendIpAddress': '127.0.0.1', +}] From 16f54e7581ba3966a127728005c7838ba3afeb5c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 19:00:09 -0500 Subject: [PATCH 21/58] Adds the first firewall CLI test. I made firewall an non-happy case in the fixtures due to them basically being tacked onto the already-existing VLAN structure. --- SoftLayer/tests/CLI/modules/firewall_tests.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 SoftLayer/tests/CLI/modules/firewall_tests.py diff --git a/SoftLayer/tests/CLI/modules/firewall_tests.py b/SoftLayer/tests/CLI/modules/firewall_tests.py new file mode 100644 index 000000000..1969bdeb5 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/firewall_tests.py @@ -0,0 +1,55 @@ +""" + SoftLayer.tests.CLI.modules.firewall_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +# from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +# from SoftLayer.CLI.exceptions import CLIAbort +from SoftLayer.CLI.modules import firewall + + +class FirewallTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_firewalls(self): + call = self.client['Account'].getNetworkVlans + call.return_value = [{ + 'id': 1, + 'dedicatedFirewallFlag': True, + 'highAvailabilityFirewallFlag': True, + 'networkVlanFirewall': {'id': 1234}, + }, { + 'id': 2, + 'dedicatedFirewallFlag': False, + 'firewallGuestNetworkComponents': [{ + 'id': 1234, + 'guestNetworkComponent': {'guest': {'id': 1}}, + 'status': 'ok'}], + 'firewallNetworkComponents': [{ + 'id': 1234, + 'networkComponent': {'downlinkComponent': {'hardwareId': 1}}, + 'status': 'ok'}], + } + ] + command = firewall.FWList(client=self.client) + + output = command.execute({}) + + self.assertEqual([{'type': 'VLAN - dedicated', + 'server/vlan id': 1, + 'features': ['HA'], + 'firewall id': 'vlan:1234'}, + {'features': '-', + 'firewall id': 'cci:1234', + 'server/vlan id': 1, + 'type': 'CCI - standard'}, + {'features': '-', + 'firewall id': 'server:1234', + 'server/vlan id': 1, + 'type': 'Server - standard'}], + format_output(output, 'python')) From 93a275467e3c533b9ed82555268eb7b6b12732b8 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 25 Apr 2014 19:02:07 -0500 Subject: [PATCH 22/58] Adds first firewall CLI tests --- SoftLayer/CLI/modules/firewall.py | 36 +++++++++++++--------- SoftLayer/managers/firewall.py | 24 ++++++--------- SoftLayer/tests/fixtures/Account.py | 11 ------- SoftLayer/tests/managers/firewall_tests.py | 17 ++++++++-- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py index f374d82f5..b4e4b93f3 100755 --- a/SoftLayer/CLI/modules/firewall.py +++ b/SoftLayer/CLI/modules/firewall.py @@ -14,13 +14,15 @@ # :license: MIT, see LICENSE for more details. from __future__ import print_function -from SoftLayer import FirewallManager, SoftLayerError -from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm -from SoftLayer.CLI.helpers import blank, CLIAbort from subprocess import call import os import tempfile +from SoftLayer import FirewallManager, SoftLayerError +from SoftLayer.utils import lookup +from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm +from SoftLayer.CLI.helpers import blank, CLIAbort + DELIMITER = "=========================================\n" @@ -217,12 +219,11 @@ def execute(self, args): 'type', 'features', 'server/vlan id']) - fwvlans = mgr.get_firewalls() - dedicatedfws = [firewall for firewall in fwvlans - if firewall['dedicatedFirewallFlag']] + dedicated_firewalls = [firewall for firewall in fwvlans + if firewall['dedicatedFirewallFlag']] - for vlan in dedicatedfws: + for vlan in dedicated_firewalls: features = [] if vlan['highAvailabilityFirewallFlag']: features.append('HA') @@ -242,10 +243,11 @@ def execute(self, args): shared_vlan = [firewall for firewall in fwvlans if not firewall['dedicatedFirewallFlag']] for vlan in shared_vlan: - fwls = [guest for guest in vlan['firewallGuestNetworkComponents'] - if has_firewall_component(guest)] + vs_firewalls = [guest + for guest in vlan['firewallGuestNetworkComponents'] + if has_firewall_component(guest)] - for firewall in fwls: + for firewall in vs_firewalls: table.add_row([ 'cci:%s' % firewall['id'], 'CCI - standard', @@ -253,15 +255,19 @@ def execute(self, args): firewall['guestNetworkComponent']['guest']['id'] ]) - fwls = [server for server in vlan['firewallNetworkComponents'] - if has_firewall_component(server)] + server_firewalls = [server + for server in vlan['firewallNetworkComponents'] + if has_firewall_component(server)] - for fwl in fwls: + for firewall in server_firewalls: table.add_row([ - 'server:%s' % fwl['id'], + 'server:%s' % firewall['id'], 'Server - standard', '-', - fwl['networkComponent']['downlinkComponent']['hardwareId'] + lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') ]) return table diff --git a/SoftLayer/managers/firewall.py b/SoftLayer/managers/firewall.py index 49bc37366..a2bf90e42 100644 --- a/SoftLayer/managers/firewall.py +++ b/SoftLayer/managers/firewall.py @@ -167,20 +167,16 @@ def get_firewalls(self): :returns: A list of firewalls on the current account. """ - results = self.account.getObject( - mask={ - 'networkVlans': { - 'firewallNetworkComponents': None, - 'networkVlanFirewall': None, - 'dedicatedFirewallFlag': None, - 'firewallGuestNetworkComponents': None, - 'firewallInterfaces': {}, - 'firewallRules': None, - 'highAvailabilityFirewallFlag': None, - # 'primarySubnet': None, - } - })['networkVlans'] - return [firewall for firewall in results + mask = ('firewallNetworkComponents,' + 'networkVlanFirewall,' + 'dedicatedFirewallFlag,' + 'firewallGuestNetworkComponents,' + 'firewallInterfaces,' + 'firewallRules,' + 'highAvailabilityFirewallFlag') + + return [firewall + for firewall in self.account.getNetworkVlans(mask=mask) if has_firewall(firewall)] def get_standard_fwl_rules(self, firewall_id): diff --git a/SoftLayer/tests/fixtures/Account.py b/SoftLayer/tests/fixtures/Account.py index f6ecaf79f..1c8d8ebae 100644 --- a/SoftLayer/tests/fixtures/Account.py +++ b/SoftLayer/tests/fixtures/Account.py @@ -181,15 +181,6 @@ 'updateDate': '2014-03-07T13:52:31-06:00'}] getObject = { - 'networkVlans': [{ - 'firewallNetworkComponents': [{'id': 1234}], - 'networkVlanFirewall': [{'id': 1234}], - 'dedicatedFirewallFlag': True, - 'firewallGuestNetworkComponents': [{'id': 1234}], - 'firewallInterfaces': [{'id': 1234}], - 'firewallRules': [{'id': 1234}], - 'highAvailabilityFirewallFlag': True, - }], 'cdnAccounts': [ { "cdnAccountName": "1234a", @@ -266,8 +257,6 @@ 'subnetType': 'PRIMARY' }] -getNetworkVlans = {'id': 1234} - getSshKeys = [{'id': '100', 'label': 'Test 1'}, {'id': '101', 'label': 'Test 2', 'notes': 'Test notes', diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 066372af3..1d079a15b 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -6,7 +6,7 @@ """ from SoftLayer import FirewallManager from SoftLayer.tests import unittest, FixtureClient -from SoftLayer.tests.fixtures import (Account, Network_Component_Firewall, +from SoftLayer.tests.fixtures import (Network_Component_Firewall, Network_Vlan_Firewall, Billing_Item) from mock import ANY @@ -23,12 +23,23 @@ def setUp(self): self.firewall = FirewallManager(self.client) def test_get_firewalls(self): - call = self.client['Account'].getObject + call = self.client['Account'].getNetworkVlans + firewall_vlan = { + 'id': 1, + 'firewallNetworkComponents': [{'id': 1234}], + 'networkVlanFirewall': {'id': 1234}, + 'dedicatedFirewallFlag': True, + 'firewallGuestNetworkComponents': [{'id': 1234}], + 'firewallInterfaces': [{'id': 1234}], + 'firewallRules': [{'id': 1234}], + 'highAvailabilityFirewallFlag': True, + } + call.return_value = [firewall_vlan] firewalls = self.firewall.get_firewalls() call.assert_called_once_with(mask=ANY) - self.assertEqual(firewalls, Account.getObject['networkVlans']) + self.assertEqual(firewalls, [firewall_vlan]) def test_get_standard_fwl_rules(self): call = self.client['Network_Component_Firewall'].getRules From d184d5df2e48ecf29af4beed982cfe4d12c51832 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 28 Apr 2014 09:00:24 -0500 Subject: [PATCH 23/58] Adds a few tests for vs module update_with_template_args() now takes a list_args argument to specify which arguments should be treated as a comma-separated list. Bumped required coverage percentage --- SoftLayer/CLI/modules/server.py | 11 +- SoftLayer/CLI/modules/vs.py | 11 +- SoftLayer/CLI/template.py | 44 +++--- SoftLayer/tests/CLI/helper_tests.py | 4 +- SoftLayer/tests/CLI/modules/server_tests.py | 18 --- SoftLayer/tests/CLI/modules/vs_tests.py | 132 ++++++++++++++++++ SoftLayer/tests/fixtures/Virtual_Guest.py | 8 +- .../tests/fixtures/sample_vs_template.conf | 3 +- setup.cfg | 2 +- 9 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 SoftLayer/tests/CLI/modules/vs_tests.py diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index eb4e66e81..4001c1996 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -779,17 +779,8 @@ class CreateServer(CLIRunnable): '--memory', '--os'] def execute(self, args): - update_with_template_args(args) + update_with_template_args(args, list_args=['--disk', '--key']) mgr = HardwareManager(self.client) - - # Disks will be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--disk'), str): - args['--disk'] = args.get('--disk').split(',') - - # Do the same thing for SSH keys - if isinstance(args.get('--key'), str): - args['--key'] = args.get('--key').split(',') - self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) diff --git a/SoftLayer/CLI/modules/vs.py b/SoftLayer/CLI/modules/vs.py index 0d6c05e5d..d1e1a356e 100755 --- a/SoftLayer/CLI/modules/vs.py +++ b/SoftLayer/CLI/modules/vs.py @@ -383,18 +383,9 @@ class CreateVS(CLIRunnable): required_params = ['--hostname', '--domain', '--cpu', '--memory'] def execute(self, args): - update_with_template_args(args) + update_with_template_args(args, list_args=['--disk', '--key']) vsi = VSManager(self.client) self._update_with_like_args(args) - - # Disks will be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--disk'), str): - args['--disk'] = args.get('--disk').split(',') - - # SSH keys may be a comma-separated list. Let's make it a real list. - if isinstance(args.get('--key'), str): - args['--key'] = args.get('--key').split(',') - self._validate_args(args) # Do not create a virtual server with --test or --export diff --git a/SoftLayer/CLI/template.py b/SoftLayer/CLI/template.py index 657ca4b80..35a718e63 100644 --- a/SoftLayer/CLI/template.py +++ b/SoftLayer/CLI/template.py @@ -13,29 +13,35 @@ from SoftLayer.utils import configparser, StringIO -def update_with_template_args(args): +def update_with_template_args(args, list_args=None): """ Populates arguments with arguments from the template file, if provided. :param dict args: command-line arguments """ - if args.get('--template'): - template_path = args.pop('--template') - if not os.path.exists(template_path): - raise ArgumentError( - 'File does not exist [-t | --template] = %s' - % template_path) - - config = configparser.ConfigParser() - ini_str = '[settings]\n' + open( - os.path.expanduser(template_path), 'r').read() - ini_fp = StringIO(ini_str) - config.readfp(ini_fp) - - # Merge template options with the options passed in - for key, value in config.items('settings'): - option_key = '--%s' % key - if not args.get(option_key): - args[option_key] = value + if not args.get('--template'): + return + + list_args = list_args or [] + + template_path = args.pop('--template') + if not os.path.exists(template_path): + raise ArgumentError( + 'File does not exist [-t | --template] = %s' + % template_path) + + config = configparser.ConfigParser() + ini_str = '[settings]\n' + open( + os.path.expanduser(template_path), 'r').read() + ini_fp = StringIO(ini_str) + config.readfp(ini_fp) + + # Merge template options with the options passed in + for key, value in config.items('settings'): + option_key = '--%s' % key + if option_key in list_args: + value = value.split(',') + if not args.get(option_key): + args[option_key] = value def export_to_template(filename, args, exclude=None): diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 5560d4fa6..69a9ad03e 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -377,8 +377,9 @@ def test_template_options(self): '--memory': '32', '--template': path, '--hourly': False, + '--disk': [], } - cli.helpers.update_with_template_args(args) + cli.helpers.update_with_template_args(args, list_args=['--disk']) self.assertEqual(args, { '--cpu': '4', '--datacenter': 'dal05', @@ -390,6 +391,7 @@ def test_template_options(self): '--network': '100', '--os': 'DEBIAN_7_64', 'key': 'value', + '--disk': ['50', '100'], }) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 90dfd91fe..df6ae28fb 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -519,15 +519,6 @@ def test_CreateServer(self): self.assertEqual(expected, format_output(output, 'python')) - # And make sure we can pass in disk and SSH keys as comma separated - # strings, which is what templates do - args['--disk'] = '1000_DRIVE,1000_DRIVE' - args['--key'] = '123,456' - - output = runnable.execute(args) - - self.assertEqual(expected, format_output(output, 'python')) - # Test explicitly setting a RAID configuration args['--controller'] = 'RAID0' @@ -691,15 +682,6 @@ def test_CreateServer_for_bmc(self, bmpi, packages): self.assertEqual(expected, format_output(output, 'python')) - # And make sure we can pass in disk and SSH keys as comma separated - # strings, which is what templates do - args['--disk'] = '1000_DRIVE,1000_DRIVE' - args['--key'] = '123,456' - - output = runnable.execute(args) - - self.assertEqual(expected, format_output(output, 'python')) - # Test explicitly setting a RAID configuration args['--controller'] = 'RAID0' diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py new file mode 100644 index 000000000..26f934654 --- /dev/null +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -0,0 +1,132 @@ +""" + SoftLayer.tests.CLI.modules.vs_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from mock import patch + +from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.CLI.helpers import format_output +from SoftLayer.CLI.modules import vs + + +class DnsTests(unittest.TestCase): + def setUp(self): + self.client = FixtureClient() + + def test_list_vs(self): + command = vs.ListVSIs(client=self.client) + + output = command.execute({'--tags': 'tag'}) + self.assertEqual([{'datacenter': 'TEST00', + 'primary_ip': '172.16.240.2', + 'host': 'vs-test1.test.sftlyr.ws', + 'memory': 1024, + 'cores': 2, + 'active_transaction': None, + 'id': 100, + 'backend_ip': '10.45.19.37'}, + {'datacenter': 'TEST00', + 'primary_ip': '172.16.240.7', + 'host': 'vs-test2.test.sftlyr.ws', + 'memory': 4096, + 'cores': 4, + 'active_transaction': None, + 'id': 104, + 'backend_ip': '10.45.19.35'}], + format_output(output, 'python')) + + def test_detail_vs(self): + command = vs.VSDetails(client=self.client) + + output = command.execute({'': '100', + '--passwords': True, + '--price': True}) + + self.assertEqual({'active_transaction': None, + 'cores': 2, + 'created': '2013-08-01 15:23:45', + 'datacenter': 'TEST00', + 'hostname': 'vs-test1.test.sftlyr.ws', + 'id': 100, + 'memory': 1024, + 'modified': {}, + 'os': '12.04-64 Minimal for CCI', + 'os_version': '12.04-64 Minimal for CCI', + 'price rate': {}, + 'notes': 'notes', + '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': [{'password': 'pass', 'username': 'user'}], + 'vlans': [{'type': 'PUBLIC', + 'number': 23, + 'id': 1}]}, + format_output(output, 'python')) + + def test_create_options(self): + command = vs.CreateOptionsVS(client=self.client) + + output = command.execute({'--all': True, + '--cpu': True, + '--datacenter': True, + '--disk': True, + '--memory': True, + '--nic': True, + '--os': True}) + + self.assertEqual({'cpus (private)': [], + 'cpus (standard)': ['1', '2', '3', '4'], + 'datacenter': ['ams01', 'dal05'], + 'local disk(0)': ['25', '100'], + 'memory': ['1024', '2048', '3072', '4096'], + 'nic': ['10', '100', '1000'], + 'os (CENTOS)': 'CENTOS_6_64', + 'os (DEBIAN)': 'DEBIAN_7_64', + 'os (UBUNTU)': 'UBUNTU_12_64'}, + format_output(output, 'python')) + + @patch('SoftLayer.CLI.modules.vs.confirm') + def test_create(self, confirm_mock): + confirm_mock.return_value = True + command = vs.CreateVS(client=self.client) + + output = command.execute({'--cpu': '2', + '--domain': 'example.com', + '--hostname': 'host', + '--image': None, + '--os': 'UBUNTU_LATEST', + '--memory': '1024', + '--nic': '100', + '--hourly': True, + '--monthly': False, + '--like': None, + '--datacenter': None, + '--dedicated': False, + '--san': False, + '--test': False, + '--export': None, + '--userfile': None, + '--postinstall': None, + '--key': [], + '--like': [], + '--network': [], + '--disk': [], + '--private': False, + '--template': None, + '--userdata': None, + '--vlan_public': None, + '--vlan_private': None, + '--wait': None, + '--really': False}) + + self.assertEqual([{'guid': '1a2b3c-1701', + 'id': 100, + 'created': '2013-08-01 15:23:45'}], + format_output(output, 'python')) diff --git a/SoftLayer/tests/fixtures/Virtual_Guest.py b/SoftLayer/tests/fixtures/Virtual_Guest.py index f12270845..ce24df919 100644 --- a/SoftLayer/tests/fixtures/Virtual_Guest.py +++ b/SoftLayer/tests/fixtures/Virtual_Guest.py @@ -18,11 +18,17 @@ 'blockDevices': [{"device": 0, "uuid": 1}, {"device": 1}, {"device": 2, "uuid": 2}], + 'notes': 'notes', + 'networkVlans': [{'networkSpace': 'PUBLIC', + 'vlanNumber': 23, + 'id': 1}], 'operatingSystem': { + 'passwords': [{'username': 'user', 'password': 'pass'}], 'softwareLicense': { 'softwareDescription': {'version': '12.04-64 Minimal for CCI', 'name': 'Ubuntu'}} - } + }, + 'tagReferences': [{'tag': {'name': 'production'}}], } getCreateObjectOptions = { diff --git a/SoftLayer/tests/fixtures/sample_vs_template.conf b/SoftLayer/tests/fixtures/sample_vs_template.conf index dc07ba1b4..90b0edf52 100644 --- a/SoftLayer/tests/fixtures/sample_vs_template.conf +++ b/SoftLayer/tests/fixtures/sample_vs_template.conf @@ -6,4 +6,5 @@ memory = 1024 os = DEBIAN_7_64 network = 100 hourly = true -monthly= false \ No newline at end of file +monthly= false +disk=50,100 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 4df91efba..d53bef49e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ verbosity=2 detailed-errors=1 with-coverage=1 -cover-min-percentage=66 +cover-min-percentage=74 cover-erase=true cover-package=SoftLayer cover-html=1 From 7a0fb48a818eeced770515eafe7afd2cb4404c00 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 8 May 2014 10:33:06 -0500 Subject: [PATCH 24/58] Re-adds some fixures --- SoftLayer/tests/CLI/modules/dns_tests.py | 15 +++++++++------ SoftLayer/tests/fixtures/Dns_Domain.py | 7 ++++++- SoftLayer/tests/managers/dns_tests.py | 10 +++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 231b8141a..053a357dc 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -7,6 +7,7 @@ from mock import patch from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests.fixtures import Dns_Domain from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import dns @@ -56,12 +57,12 @@ def test_list_all_zones(self): command = dns.ListZones(client=self.client) output = command.execute({'': 'example.com'}) - self.assertEqual([{'id': 1, - 'record': 'hostname', - 'ttl': 100, - 'type': 'A', - 'value': 'd'}], - format_output(output, 'python')) + self.assertEqual({'record': 'a', + 'type': 'CNAME', + 'id': 1, + 'value': 'd', + 'ttl': 100}, + format_output(output, 'python')[0]) def test_add_record(self): command = dns.AddRecord(client=self.client) @@ -95,6 +96,8 @@ def test_edit_record(self): @patch('SoftLayer.CLI.modules.dns.no_going_back') def test_delete_record(self, no_going_back_mock): no_going_back_mock.return_value = True + self.client['Dns_Domain'].getResourceRecords.return_value = [ + Dns_Domain.getResourceRecords[0]] command = dns.RecordRemove(client=self.client) output = command.execute({'': 'example.com', '': 'hostname', diff --git a/SoftLayer/tests/fixtures/Dns_Domain.py b/SoftLayer/tests/fixtures/Dns_Domain.py index f9c5106ad..998549458 100644 --- a/SoftLayer/tests/fixtures/Dns_Domain.py +++ b/SoftLayer/tests/fixtures/Dns_Domain.py @@ -3,6 +3,11 @@ editObject = True getZoneFileContents = 'lots of text' getResourceRecords = [ - {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'hostname', 'type': 'a'}, + {'id': 1, 'ttl': 7200, 'data': 'd', 'host': 'a', 'type': 'cname'}, + {'id': 2, 'ttl': 900, 'data': '1', 'host': 'b', 'type': 'a'}, + {'id': 3, 'ttl': 900, 'data': 'x', 'host': 'c', 'type': 'ptr'}, + {'id': 4, 'ttl': 86400, 'data': 'b', 'host': 'd', 'type': 'txt'}, + {'id': 5, 'ttl': 86400, 'data': 'b', 'host': 'e', 'type': 'txt'}, + {'id': 6, 'ttl': 600, 'data': 'b', 'host': 'f', 'type': 'txt'}, ] getObject = {'id': 98765, 'name': 'test-example.com'} diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index 708212441..3d79c766c 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -105,20 +105,20 @@ def test_dump_zone(self): f.assert_called_once_with(id=1) def test_get_record(self): - D = self.client['Dns_Domain'].getResourceRecords + records = self.client['Dns_Domain'].getResourceRecords # maybe valid domain, but no records matching - D.return_value = [] + records.return_value = [] self.assertEqual(self.dns_client.get_records(12345), []) - D.reset_mock() - D.return_value = [Dns_Domain.getResourceRecords[0]] + records.reset_mock() + records.return_value = [Dns_Domain.getResourceRecords[0]] self.dns_client.get_records(12345, record_type='a', host='hostname', data='a', ttl='86400') - D.assert_called_once_with( + records.assert_called_once_with( id=12345, filter={'resourceRecords': {'type': {'operation': '_= a'}, 'host': {'operation': '_= hostname'}, From f0f6657f0e74f50cec2f1117ab261ff1a3bc3c2c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 8 May 2014 10:46:29 -0500 Subject: [PATCH 25/58] Fixes pylint errors --- SoftLayer/managers/vs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 9f158f311..8bcd9e184 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -567,6 +567,7 @@ def upgrade(self, instance_id, cpus=None, memory=None, if nic_speed: item_id.append({'id': self._get_item_id_for_upgrade( package_items, 'nic_speed', nic_speed)}) + order = {} order['complexType'] = \ 'SoftLayer_Container_Product_Order_Virtual_Guest_Upgrade' From 257d62686ac0fd26ca6ee296199ac3e9f6a32ef3 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 14 May 2014 16:47:07 -0400 Subject: [PATCH 26/58] Sort Imports --- SoftLayer/tests/managers/queue_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index 2851c4cf6..aa75ae96a 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -5,12 +5,12 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import MessagingManager, Unauthenticated, SoftLayerError -import SoftLayer.managers.messaging from SoftLayer.consts import USER_AGENT from SoftLayer.tests import unittest +import SoftLayer.managers.messaging -import json from mock import MagicMock, patch, ANY +import json QUEUE_1 = { 'expiration': 40000, From 329c5e42d9e142f56a6a6a9491da68e9238920e0 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 14 May 2014 16:54:26 -0400 Subject: [PATCH 27/58] Remove Unused json import (cause merging and stuff) --- SoftLayer/tests/managers/queue_tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index aa75ae96a..5ed2db0ab 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -10,7 +10,6 @@ import SoftLayer.managers.messaging from mock import MagicMock, patch, ANY -import json QUEUE_1 = { 'expiration': 40000, From 9fca20965bd57cfe7ed80d7ffb0387fd12f3fcb0 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 15 May 2014 20:30:21 -0400 Subject: [PATCH 28/58] Adds Python 3.4 testing with travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index aabd7e374..b9d9610f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ env: - TOX_ENV=py26 - TOX_ENV=py27 - TOX_ENV=py33 + - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - TOX_ENV=pylint From 48c0274e72f0fdb908ed1cb90c8d0c0a0fa0e116 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 10:51:26 -0500 Subject: [PATCH 29/58] Fixes #336 --- SoftLayer/CLI/modules/nas.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py index d252b61b6..cfc59804e 100644 --- a/SoftLayer/CLI/modules/nas.py +++ b/SoftLayer/CLI/modules/nas.py @@ -9,7 +9,8 @@ # :license: MIT, see LICENSE for more details. from SoftLayer.CLI import CLIRunnable, Table, FormattedItem -from SoftLayer.CLI.helpers import NestedDict, blank +from SoftLayer.CLI.helpers import blank +from SoftLayer.utils import lookup class ListNAS(CLIRunnable): @@ -27,7 +28,6 @@ def execute(self, args): nas_accounts = account.getNasNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') - nas_accounts = [NestedDict(n) for n in nas_accounts] table = Table(['id', 'datacenter', 'size', 'username', 'password', 'server']) @@ -35,8 +35,10 @@ def execute(self, args): for nas_account in nas_accounts: table.add_row([ nas_account['id'], - nas_account['serviceResource']['datacenter'].get('name', - blank()), + lookup(nas_account, + 'serviceResource', + 'datacenter', + 'name') or blank(), FormattedItem( nas_account.get('capacityGb', blank()), "%dGB" % nas_account.get('capacityGb', 0)), From cfb35d00bbd66779f616aa3c3e310a2c42a1ba37 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 11:25:28 -0500 Subject: [PATCH 30/58] Adds some nice text around unexpected exceptions --- SoftLayer/CLI/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 67bcb865b..89b457372 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -244,7 +244,9 @@ def main(args=sys.argv[1:], env=Environment()): exit_status = 1 except Exception: import traceback + env.err("An unexpected error has occured:") env.err(traceback.format_exc()) + env.err("Feel free to report this error as it is likely a bug.") exit_status = 1 sys.exit(exit_status) From d87806f3bbb1b677dfd85b2158a8ce5a82e2ec83 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 13:27:45 -0500 Subject: [PATCH 31/58] Skip OS descriptions that cannot be parsed --- SoftLayer/CLI/modules/server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 4001c1996..59108e98b 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -608,6 +608,11 @@ def get_create_options(self, ds_options, section, pretty=True): os_code = self._generate_windows_code(opsys['description']) else: os_results = os_regex.search(opsys['description']) + + # Skip this operating system if it's not parsable + if os_results is None: + continue + name = os_results.group(1) version = os_results.group(2) bits = bit_regex.search(opsys['description']) From d51e9f27aab108dcf998dd8e4bde16a9b70e73f4 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 21 May 2014 13:32:31 -0500 Subject: [PATCH 32/58] Increase max statements allowed by pylint :( --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1ac9f3154..76bc4f943 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ commands = pylint SoftLayer \ -d W0142 \ # Used * or ** magic --max-args=20 \ --max-branches=40 \ - --max-statements=85 \ + --max-statements=86 \ --max-module-lines=1200 \ --max-returns=8 \ --min-similarity-lines=50 # TODO: Remove \ No newline at end of file From 17bfff4bf63f12a0cec50f1f3cc38326cdfbb8fd Mon Sep 17 00:00:00 2001 From: njain Date: Wed, 21 May 2014 13:39:14 -0500 Subject: [PATCH 33/58] Adding list_ecsci function to iscsi manager --- SoftLayer/CLI/modules/iscsi.py | 9 ++------- SoftLayer/managers/iscsi.py | 8 ++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index fe83b4d1f..cc916f98d 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -28,12 +28,9 @@ class ListISCSIs(CLIRunnable): action = 'list' def execute(self, args): - account = self.client['Account'] - - iscsi_list = account.getIscsiNetworkStorage( - mask='eventCount,serviceResource[datacenter.name]') + iscsi_mgr = ISCSIManager(self.client) + iscsi_list = iscsi_mgr.list_iscsi() iscsi_list = [NestedDict(n) for n in iscsi_list] - table = Table([ 'id', 'datacenter', @@ -42,7 +39,6 @@ def execute(self, args): 'password', 'server' ]) - for iscsi in iscsi_list: table.add_row([ iscsi['id'], @@ -53,7 +49,6 @@ def execute(self, args): iscsi.get('username', blank()), iscsi.get('password', blank()), iscsi.get('serviceResourceBackendIpAddress', blank())]) - return table diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index bd640ab41..4ef19ca17 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -69,6 +69,14 @@ def create_iscsi(self, size=None, location=None): self.product_order.verifyOrder(iscsi_order) self.product_order.placeOrder(iscsi_order) + def list_iscsi(self): + """List iSCSI volume + """ + account = self.client['Account'] + iscsi_list = account.getIscsiNetworkStorage( + mask='eventCount,serviceResource[datacenter.name]') + return iscsi_list + def get_iscsi(self, volume_id, **kwargs): """ Get details about a iSCSI storage From 5488bc0917f0d79212f1ca232bf1f922d4ddac04 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 22 May 2014 16:07:19 -0500 Subject: [PATCH 34/58] Adds link to gh issues on unexpected error --- SoftLayer/CLI/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 89b457372..c2e5f7fc4 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -246,7 +246,8 @@ def main(args=sys.argv[1:], env=Environment()): import traceback env.err("An unexpected error has occured:") env.err(traceback.format_exc()) - env.err("Feel free to report this error as it is likely a bug.") + env.err("Feel free to report this error as it is likely a bug:") + env.err(" https://github.com/softlayer/softlayer-python/issues") exit_status = 1 sys.exit(exit_status) From b69e92f314647014c76226781104fd5556518bf5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 16 May 2014 12:02:54 -0400 Subject: [PATCH 35/58] Adds pep8-naming to pep8 tests. Adds py3 and py2 pep8 tests --- .travis.yml | 2 +- SoftLayer/exceptions.py | 9 ++-- SoftLayer/tests/CLI/core_tests.py | 20 ++++----- SoftLayer/tests/CLI/environment_tests.py | 6 +-- SoftLayer/tests/CLI/helper_tests.py | 22 +++++----- SoftLayer/tests/CLI/modules/cdn_tests.py | 6 +-- SoftLayer/tests/CLI/modules/config_tests.py | 10 ++--- SoftLayer/tests/CLI/modules/dns_tests.py | 6 +-- SoftLayer/tests/CLI/modules/firewall_tests.py | 6 +-- SoftLayer/tests/CLI/modules/globalip_tests.py | 6 +-- SoftLayer/tests/CLI/modules/help_tests.py | 4 +- SoftLayer/tests/CLI/modules/import_tests.py | 4 +- SoftLayer/tests/CLI/modules/nas_tests.py | 6 +-- SoftLayer/tests/CLI/modules/rwhois_tests.py | 6 +-- SoftLayer/tests/CLI/modules/server_tests.py | 44 +++++++++---------- SoftLayer/tests/CLI/modules/sshkey_tests.py | 6 +-- SoftLayer/tests/CLI/modules/summary_tests.py | 6 +-- SoftLayer/tests/CLI/modules/vs_tests.py | 6 +-- SoftLayer/tests/__init__.py | 14 ++++++ SoftLayer/tests/api_tests.py | 18 ++++---- SoftLayer/tests/auth_tests.py | 12 ++--- SoftLayer/tests/basic_tests.py | 14 +++--- SoftLayer/tests/config_tests.py | 10 ++--- SoftLayer/tests/functional_tests.py | 6 +-- SoftLayer/tests/managers/cci_tests.py | 6 +-- SoftLayer/tests/managers/cdn_tests.py | 6 +-- SoftLayer/tests/managers/dns_tests.py | 6 +-- SoftLayer/tests/managers/firewall_tests.py | 18 ++++---- SoftLayer/tests/managers/hardware_tests.py | 6 +-- SoftLayer/tests/managers/image_tests.py | 6 +-- SoftLayer/tests/managers/iscsi_tests.py | 14 +++--- SoftLayer/tests/managers/loadbal_tests.py | 6 +-- SoftLayer/tests/managers/metadata_tests.py | 10 ++--- SoftLayer/tests/managers/network_tests.py | 6 +-- SoftLayer/tests/managers/queue_tests.py | 14 +++--- SoftLayer/tests/managers/sshkey_tests.py | 6 +-- SoftLayer/tests/managers/ssl_tests.py | 6 +-- SoftLayer/tests/managers/ticket_tests.py | 6 +-- SoftLayer/tests/managers/vs_tests.py | 16 +++---- SoftLayer/tests/transport_tests.py | 8 ++-- tox.ini | 42 +++++++++--------- 41 files changed, 222 insertions(+), 209 deletions(-) diff --git a/.travis.yml b/.travis.yml index b9d9610f1..a736003a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,6 @@ env: - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - - TOX_ENV=pylint + - TOX_ENV=py2pep8 install: pip install tox --use-mirrors script: tox -e $TOX_ENV diff --git a/SoftLayer/exceptions.py b/SoftLayer/exceptions.py index 5d86a9b35..7227168af 100644 --- a/SoftLayer/exceptions.py +++ b/SoftLayer/exceptions.py @@ -5,6 +5,7 @@ :license: MIT, see LICENSE for more details. """ +# pylint: disable=C0103 class SoftLayerError(Exception): @@ -21,10 +22,10 @@ class SoftLayerAPIError(SoftLayerError): Provides faultCode and faultString properties. """ - def __init__(self, faultCode, faultString, *args): - SoftLayerError.__init__(self, faultString, *args) - self.faultCode = faultCode # pylint: disable=C0103 - self.reason = self.faultString = faultString # pylint: disable=C0103 + def __init__(self, fault_code, fault_string, *args): + SoftLayerError.__init__(self, fault_string, *args) + self.faultCode = fault_code + self.reason = self.faultString = fault_string def __repr__(self): return '<%s(%s): %s>' % \ diff --git a/SoftLayer/tests/CLI/core_tests.py b/SoftLayer/tests/CLI/core_tests.py index 0e8a3bc7d..b700568c9 100644 --- a/SoftLayer/tests/CLI/core_tests.py +++ b/SoftLayer/tests/CLI/core_tests.py @@ -8,7 +8,7 @@ import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI import core from SoftLayer.CLI.helpers import CLIAbort from SoftLayer.CLI.environment import Environment, InvalidModule, CLIRunnable @@ -28,7 +28,7 @@ def module_no_command_fixture(): """ -class submodule_fixture(CLIRunnable): +class SubmoduleFixture(CLIRunnable): """ usage: sl vs list [options] @@ -44,7 +44,7 @@ def execute(self, args): class EnvironmentFixture(Environment): def __init__(self): super(EnvironmentFixture, self).__init__() - self.plugins = {'vs': {'list': submodule_fixture}} + self.plugins = {'vs': {'list': SubmoduleFixture}} self.aliases = { 'meta': 'metadata', 'my': 'metadata', @@ -58,8 +58,8 @@ def plugin_list(self, *args, **kwargs): return self.plugins.keys() -class CommandLineTests(unittest.TestCase): - def setUp(self): +class CommandLineTests(TestCase): + def set_up(self): self.env = EnvironmentFixture() self.env.get_module_name = MagicMock() @@ -105,19 +105,19 @@ def test_invalid_module(self): def test_module_with_no_command(self): self.env.plugins = { - 'vs': {'list': submodule_fixture, None: submodule_fixture} + 'vs': {'list': SubmoduleFixture, None: SubmoduleFixture} } self.env.get_module_name.return_value = 'vs' self.env.load_module = MagicMock() self.env.load_module.return_value = module_no_command_fixture resolver = core.CommandParser(self.env) command, command_args = resolver.parse(['vs', 'list']) - self.assertEqual(submodule_fixture, command) + self.assertEqual(SubmoduleFixture, command) def test_main(self): self.env.get_module_name.return_value = 'vs' self.env.plugins = { - 'vs': {'list': submodule_fixture} + 'vs': {'list': SubmoduleFixture} } self.assertRaises( SystemExit, core.main, @@ -181,8 +181,8 @@ def test_uncaught_error(self, m): m.assert_called_once_with() -class TestCommandParser(unittest.TestCase): - def setUp(self): +class TestCommandParser(TestCase): + def set_up(self): self.env = EnvironmentFixture() self.parser = core.CommandParser(self.env) diff --git a/SoftLayer/tests/CLI/environment_tests.py b/SoftLayer/tests/CLI/environment_tests.py index d7b0e3471..bdc19e0bf 100644 --- a/SoftLayer/tests/CLI/environment_tests.py +++ b/SoftLayer/tests/CLI/environment_tests.py @@ -7,13 +7,13 @@ import os from mock import patch, MagicMock -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.environment import Environment, InvalidCommand -class EnvironmentTests(unittest.TestCase): +class EnvironmentTests(TestCase): - def setUp(self): + def set_up(self): self.env = Environment() def test_plugin_list(self): diff --git a/SoftLayer/tests/CLI/helper_tests.py b/SoftLayer/tests/CLI/helper_tests.py index 69a9ad03e..f24291753 100644 --- a/SoftLayer/tests/CLI/helper_tests.py +++ b/SoftLayer/tests/CLI/helper_tests.py @@ -9,7 +9,7 @@ import json import SoftLayer.CLI as cli -from SoftLayer.tests import FIXTURE_PATH, unittest +from SoftLayer.tests import FIXTURE_PATH, TestCase from mock import patch, mock_open, call if sys.version_info >= (3,): @@ -18,7 +18,7 @@ open_path = '__builtin__.open' -class CLIJSONEncoderTest(unittest.TestCase): +class CLIJSONEncoderTest(TestCase): def test_default(self): out = json.dumps({ 'formattedItem': cli.helpers.FormattedItem('normal', 'formatted') @@ -35,7 +35,7 @@ def test_fail(self): json.dumps, {'test': object()}, cls=cli.formatting.CLIJSONEncoder) -class PromptTests(unittest.TestCase): +class PromptTests(TestCase): @patch('SoftLayer.CLI.formatting.console_input') def test_invalid_response(self, raw_input_mock): @@ -104,7 +104,7 @@ def test_confirmation(self, raw_input_mock): self.assertTrue(res) -class FormattedItemTests(unittest.TestCase): +class FormattedItemTests(TestCase): def test_init(self): item = cli.FormattedItem('test', 'test_formatted') @@ -152,7 +152,7 @@ def test_blank(self): self.assertEqual('NULL', str(item)) -class FormattedListTests(unittest.TestCase): +class FormattedListTests(TestCase): def test_init(self): l = cli.listing([1, 'two'], separator=':') self.assertEqual([1, 'two'], list(l)) @@ -180,7 +180,7 @@ def test_str(self): self.assertEqual('1:two', result) -class FormattedTxnTests(unittest.TestCase): +class FormattedTxnTests(TestCase): def test_active_txn_empty(self): self.assertRaises(KeyError, cli.active_txn, {}) @@ -229,7 +229,7 @@ def test_transaction_status_missing(self): self.assertEqual(result.original, b.original) -class CLIAbortTests(unittest.TestCase): +class CLIAbortTests(TestCase): def test_init(self): e = cli.helpers.CLIAbort("something") @@ -238,7 +238,7 @@ def test_init(self): self.assertIsInstance(e, cli.helpers.CLIHalt) -class ResolveIdTests(unittest.TestCase): +class ResolveIdTests(TestCase): def test_resolve_id_one(self): resolver = lambda r: [12345] @@ -257,7 +257,7 @@ def test_resolve_id_multiple(self): cli.helpers.CLIAbort, cli.helpers.resolve_id, resolver, 'test') -class TestFormatOutput(unittest.TestCase): +class TestFormatOutput(TestCase): def test_format_output_string(self): t = cli.helpers.format_output('just a string', 'raw') @@ -356,7 +356,7 @@ def test_format_output_python_keyvaluetable(self): self.assertEqual({'nothing': None}, ret) -class TestTemplateArgs(unittest.TestCase): +class TestTemplateArgs(TestCase): def test_no_template_option(self): args = {'key': 'value'} @@ -395,7 +395,7 @@ def test_template_options(self): }) -class TestExportToTemplate(unittest.TestCase): +class TestExportToTemplate(TestCase): def test_export_to_template(self): with patch(open_path, mock_open(), create=True) as open_: cli.helpers.export_to_template('filename', { diff --git a/SoftLayer/tests/CLI/modules/cdn_tests.py b/SoftLayer/tests/CLI/modules/cdn_tests.py index 31e66aef6..d26ae1854 100644 --- a/SoftLayer/tests/CLI/modules/cdn_tests.py +++ b/SoftLayer/tests/CLI/modules/cdn_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import cdn -class CdnTests(unittest.TestCase): - def setUp(self): +class CdnTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_accounts(self): diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py index 2bfa86667..9f23ec48b 100644 --- a/SoftLayer/tests/CLI/modules/config_tests.py +++ b/SoftLayer/tests/CLI/modules/config_tests.py @@ -9,14 +9,14 @@ from SoftLayer import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT from SoftLayer.auth import BasicAuthentication -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import config from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort -class TestHelpShow(unittest.TestCase): - def setUp(self): +class TestHelpShow(TestCase): + def set_up(self): client = MagicMock() client.auth.username = 'user' client.auth.api_key = '12345' @@ -36,8 +36,8 @@ def test_show(self): self.assertEqual(expected, format_output(output, 'python')) -class TestHelpSetup(unittest.TestCase): - def setUp(self): +class TestHelpSetup(TestCase): + def set_up(self): client = FixtureClient() client.auth = BasicAuthentication('default-user', 'default-key') client.endpoint_url = 'default-endpoint-url' diff --git a/SoftLayer/tests/CLI/modules/dns_tests.py b/SoftLayer/tests/CLI/modules/dns_tests.py index 053a357dc..fa8705909 100644 --- a/SoftLayer/tests/CLI/modules/dns_tests.py +++ b/SoftLayer/tests/CLI/modules/dns_tests.py @@ -6,15 +6,15 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Dns_Domain from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import dns -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_dump_zone(self): diff --git a/SoftLayer/tests/CLI/modules/firewall_tests.py b/SoftLayer/tests/CLI/modules/firewall_tests.py index 1969bdeb5..ef7b8b041 100644 --- a/SoftLayer/tests/CLI/modules/firewall_tests.py +++ b/SoftLayer/tests/CLI/modules/firewall_tests.py @@ -6,14 +6,14 @@ """ # from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output # from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import firewall -class FirewallTests(unittest.TestCase): - def setUp(self): +class FirewallTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_firewalls(self): diff --git a/SoftLayer/tests/CLI/modules/globalip_tests.py b/SoftLayer/tests/CLI/modules/globalip_tests.py index 8384b6280..0a6eb5cdc 100644 --- a/SoftLayer/tests/CLI/modules/globalip_tests.py +++ b/SoftLayer/tests/CLI/modules/globalip_tests.py @@ -6,14 +6,14 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort from SoftLayer.CLI.modules import globalip -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_ip_assign(self): diff --git a/SoftLayer/tests/CLI/modules/help_tests.py b/SoftLayer/tests/CLI/modules/help_tests.py index 5403d5bbf..8431dc657 100644 --- a/SoftLayer/tests/CLI/modules/help_tests.py +++ b/SoftLayer/tests/CLI/modules/help_tests.py @@ -4,12 +4,12 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.modules import help from SoftLayer.CLI.environment import Environment -class HelpTests(unittest.TestCase): +class HelpTests(TestCase): def test_help(self): command = help.Show(env=Environment()) diff --git a/SoftLayer/tests/CLI/modules/import_tests.py b/SoftLayer/tests/CLI/modules/import_tests.py index 60db8b4a4..a23f9f0d2 100644 --- a/SoftLayer/tests/CLI/modules/import_tests.py +++ b/SoftLayer/tests/CLI/modules/import_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.CLI.modules import get_module_list from importlib import import_module -class TestImportCLIModules(unittest.TestCase): +class TestImportCLIModules(TestCase): def test_import_all(self): modules = get_module_list() diff --git a/SoftLayer/tests/CLI/modules/nas_tests.py b/SoftLayer/tests/CLI/modules/nas_tests.py index 053c10331..5fd8e013e 100644 --- a/SoftLayer/tests/CLI/modules/nas_tests.py +++ b/SoftLayer/tests/CLI/modules/nas_tests.py @@ -4,13 +4,13 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import nas from SoftLayer.CLI.helpers import format_output -class RWhoisTests(unittest.TestCase): - def setUp(self): +class RWhoisTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_nas(self): diff --git a/SoftLayer/tests/CLI/modules/rwhois_tests.py b/SoftLayer/tests/CLI/modules/rwhois_tests.py index 8921bbcbd..cbb39d81a 100644 --- a/SoftLayer/tests/CLI/modules/rwhois_tests.py +++ b/SoftLayer/tests/CLI/modules/rwhois_tests.py @@ -4,14 +4,14 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import rwhois from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.exceptions import CLIAbort -class RWhoisTests(unittest.TestCase): - def setUp(self): +class RWhoisTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_edit_nothing(self): diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index df6ae28fb..7186914d6 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -7,7 +7,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import Mock, patch try: # Python 3.x compatibility @@ -21,11 +21,11 @@ from SoftLayer.CLI.modules import server -class ServerCLITests(unittest.TestCase): - def setUp(self): +class ServerCLITests(TestCase): + def set_up(self): self.client = FixtureClient() - def test_ServerCancelReasons(self): + def test_server_cancel_reasons(self): runnable = server.ServerCancelReasons(client=self.client) output = runnable.execute({}) @@ -54,7 +54,7 @@ def test_ServerCancelReasons(self): f(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions(self, packages): + def test_server_create_options(self, packages): args = { '': '999', '--all': True, @@ -101,7 +101,7 @@ def test_ServerCreateOptions(self, packages): self.assertEqual(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions_with_cpu_only(self, packages): + def test_server_create_options_with_cpu_only(self, packages): args = { '': '999', '--all': False, @@ -135,7 +135,7 @@ def test_ServerCreateOptions_with_cpu_only(self, packages): self.assertEqual(expected, format_output(output, 'python')) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ServerCreateOptions_with_invalid_chassis(self, packages): + def test_server_create_options_with_invalid_chassis(self, packages): args = { '': '999', '--all': True, @@ -159,7 +159,7 @@ def test_ServerCreateOptions_with_invalid_chassis(self, packages): @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') @patch('SoftLayer.HardwareManager.get_bare_metal_package_id') - def test_ServerCreateOptions_for_bmc(self, bmpi, packages): + def test_server_create_options_for_bmc(self, bmpi, packages): args = { '': '1099', '--all': True, @@ -246,7 +246,7 @@ def test_server_details_issue_332(self): self.assertFalse(self.client['Hardware_Server'] .getReverseDomainRecords.called) - def test_ListServers(self): + def test_list_servers(self): runnable = server.ListServers(client=self.client) output = runnable.execute({'--tags': 'openstack'}) @@ -290,7 +290,7 @@ def test_ListServers(self): @patch('SoftLayer.CLI.modules.server.no_going_back') @patch('SoftLayer.HardwareManager.reload') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_ServerReload( + def test_server_reload( self, resolve_mock, reload_mock, ngb_mock, abort_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -313,7 +313,7 @@ def test_ServerReload( @patch('SoftLayer.CLI.modules.server.no_going_back') @patch('SoftLayer.HardwareManager.cancel_hardware') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_CancelServer( + def test_cancel_server( self, resolve_mock, cancel_mock, ngb_mock, abort_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -339,7 +339,7 @@ def test_CancelServer( env_mock.assert_called() @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerPowerOff(self, confirm_mock): + def test_server_power_off(self, confirm_mock): hw_id = 12345 runnable = server.ServerPowerOff(client=self.client) @@ -356,7 +356,7 @@ def test_ServerPowerOff(self, confirm_mock): self.assertRaises(CLIAbort, runnable.execute, args) @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerReboot(self, confirm_mock): + def test_server_reboot(self, confirm_mock): hw_id = 12345 runnable = server.ServerReboot(client=self.client) @@ -387,7 +387,7 @@ def test_ServerReboot(self, confirm_mock): args['--really'] = False self.assertRaises(CLIAbort, runnable.execute, args) - def test_ServerPowerOn(self): + def test_server_power_on(self): hw_id = 12345 runnable = server.ServerPowerOn(client=self.client) @@ -400,7 +400,7 @@ def test_ServerPowerOn(self): self.client['Hardware_Server'].powerOn.assert_called_with(id=hw_id) @patch('SoftLayer.CLI.modules.server.confirm') - def test_ServerPowerCycle(self, confirm_mock): + def test_server_power_cycle(self, confirm_mock): hw_id = 12345 runnable = server.ServerPowerCycle(client=self.client) @@ -420,7 +420,7 @@ def test_ServerPowerCycle(self, confirm_mock): @patch('SoftLayer.HardwareManager.change_port_speed') @patch('SoftLayer.CLI.modules.server.resolve_id') - def test_NicEditServer(self, resolve_mock, port_mock): + def test_cic_edit_server(self, resolve_mock, port_mock): hw_id = 12345 resolve_mock.return_value = hw_id @@ -443,7 +443,7 @@ def test_NicEditServer(self, resolve_mock, port_mock): runnable.execute(args) @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') - def test_ListChassisServer(self, packages): + def test_list_chassis_server(self, packages): test_data = [ (1, 'Chassis 1'), (2, 'Chassis 2') @@ -460,7 +460,7 @@ def test_ListChassisServer(self, packages): self.assertEqual(expected, format_output(output, 'python')) - def test_CreateServer(self): + def test_create_server(self): args = { '--chassis': 999, '--hostname': 'test', @@ -549,7 +549,7 @@ def test_CreateServer(self): self.assertRaises(CLIAbort, runnable.execute, args) - def test_CreateServer_failures(self): + def test_create_server_failures(self): # This is missing a required argument args = { @@ -586,7 +586,7 @@ def test_CreateServer_failures(self): self.assertRaises(CLIAbort, runnable.execute, args) @patch('SoftLayer.CLI.modules.server.export_to_template') - def test_CreateServer_with_export(self, export_to_template): + def test_create_server_with_export(self, export_to_template): args = { '--chassis': 999, '--hostname': 'test', @@ -616,7 +616,7 @@ def test_CreateServer_with_export(self, export_to_template): @patch('SoftLayer.HardwareManager.get_available_dedicated_server_packages') @patch('SoftLayer.HardwareManager.get_bare_metal_package_id') - def test_CreateServer_for_bmc(self, bmpi, packages): + def test_create_server_for_bmc(self, bmpi, packages): args = { '--chassis': '1099', '--hostname': 'test', @@ -712,7 +712,7 @@ def test_CreateServer_for_bmc(self, bmpi, packages): self.assertRaises(CLIAbort, runnable.execute, args) - def test_EditServer(self): + def test_edit_server(self): # Test both userdata and userfile at once args = { '': 1000, diff --git a/SoftLayer/tests/CLI/modules/sshkey_tests.py b/SoftLayer/tests/CLI/modules/sshkey_tests.py index 5ec272e0c..a98ba0be4 100644 --- a/SoftLayer/tests/CLI/modules/sshkey_tests.py +++ b/SoftLayer/tests/CLI/modules/sshkey_tests.py @@ -8,14 +8,14 @@ from mock import patch import tempfile -from SoftLayer.tests import unittest, FixtureClient, FIXTURE_PATH +from SoftLayer.tests import TestCase, FixtureClient, FIXTURE_PATH from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import sshkey from SoftLayer.CLI.exceptions import CLIAbort -class SshKeyTests(unittest.TestCase): - def setUp(self): +class SshKeyTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_add_by_option(self): diff --git a/SoftLayer/tests/CLI/modules/summary_tests.py b/SoftLayer/tests/CLI/modules/summary_tests.py index c77be8bb8..4680f4d24 100644 --- a/SoftLayer/tests/CLI/modules/summary_tests.py +++ b/SoftLayer/tests/CLI/modules/summary_tests.py @@ -4,14 +4,14 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.modules import summary from SoftLayer.CLI.environment import Environment from SoftLayer.CLI.helpers import format_output -class SummaryTests(unittest.TestCase): - def setUp(self): +class SummaryTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_summary(self): diff --git a/SoftLayer/tests/CLI/modules/vs_tests.py b/SoftLayer/tests/CLI/modules/vs_tests.py index 26f934654..96bd59cf6 100644 --- a/SoftLayer/tests/CLI/modules/vs_tests.py +++ b/SoftLayer/tests/CLI/modules/vs_tests.py @@ -6,13 +6,13 @@ """ from mock import patch -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.CLI.helpers import format_output from SoftLayer.CLI.modules import vs -class DnsTests(unittest.TestCase): - def setUp(self): +class DnsTests(TestCase): + def set_up(self): self.client = FixtureClient() def test_list_vs(self): diff --git a/SoftLayer/tests/__init__.py b/SoftLayer/tests/__init__.py index f1047d958..13be79bdc 100644 --- a/SoftLayer/tests/__init__.py +++ b/SoftLayer/tests/__init__.py @@ -14,4 +14,18 @@ FIXTURE_PATH = os.path.abspath(os.path.join(__file__, '..', 'fixtures')) + +class TestCase(unittest.TestCase): + def set_up(self): + pass + + def tear_down(self): + pass + + def setUp(self): # NOQA + return self.set_up() + + def tearDown(self): # NOQA + return self.tear_down() + __all__ = ['unittest', 'FixtureClient'] diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index 630e3a77a..2c7646c95 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -8,11 +8,11 @@ import SoftLayer import SoftLayer.API -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from SoftLayer.consts import USER_AGENT -class Inititialization(unittest.TestCase): +class Inititialization(TestCase): def test_init(self): client = SoftLayer.Client(username='doesnotexist', api_key='issurelywrong', timeout=10) @@ -38,7 +38,7 @@ def test_env(self, get_client_settings): self.assertEquals(client.endpoint_url, 'http://endpoint_url') -class ClientMethods(unittest.TestCase): +class ClientMethods(TestCase): def test_help(self): help(SoftLayer) help(SoftLayer.Client) @@ -71,8 +71,8 @@ def test_len(self): self.assertEqual(len(client), 0) -class APIClient(unittest.TestCase): - def setUp(self): +class APIClient(TestCase): + def set_up(self): self.client = SoftLayer.Client( username='doesnotexist', api_key='issurelywrong', endpoint_url="ENDPOINT") @@ -304,8 +304,8 @@ def test_call_compression_override(self, make_xml_rpc_api_call): }) -class APITimedClient(unittest.TestCase): - def setUp(self): +class APITimedClient(TestCase): + def set_up(self): self.client = SoftLayer.TimedClient( username='doesnotexist', api_key='issurelywrong', endpoint_url="ENDPOINT") @@ -324,8 +324,8 @@ def test_overriden_call_times_methods(self, _call, _time): self.assertEqual(expected_calls, self.client.get_last_calls()) -class UnauthenticatedAPIClient(unittest.TestCase): - def setUp(self): +class UnauthenticatedAPIClient(TestCase): + def set_up(self): self.client = SoftLayer.Client(endpoint_url="ENDPOINT") @patch('SoftLayer.API.get_client_settings') diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py index 7da24490a..abb793511 100644 --- a/SoftLayer/tests/auth_tests.py +++ b/SoftLayer/tests/auth_tests.py @@ -6,17 +6,17 @@ """ from SoftLayer.auth import ( AuthenticationBase, BasicAuthentication, TokenAuthentication) -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestAuthenticationBase(unittest.TestCase): +class TestAuthenticationBase(TestCase): def test_get_headers(self): auth = AuthenticationBase() self.assertRaises(NotImplementedError, auth.get_headers) -class TestBasicAuthentication(unittest.TestCase): - def setUp(self): +class TestBasicAuthentication(TestCase): + def set_up(self): self.auth = BasicAuthentication('USERNAME', 'APIKEY') def test_attribs(self): @@ -37,8 +37,8 @@ def test_repr(self): self.assertIn('USERNAME', s) -class TestTokenAuthentication(unittest.TestCase): - def setUp(self): +class TestTokenAuthentication(TestCase): + def set_up(self): self.auth = TokenAuthentication(12345, 'TOKEN') def test_attribs(self): diff --git a/SoftLayer/tests/basic_tests.py b/SoftLayer/tests/basic_tests.py index f8d881bc3..5867fc891 100644 --- a/SoftLayer/tests/basic_tests.py +++ b/SoftLayer/tests/basic_tests.py @@ -6,10 +6,10 @@ :license: MIT, see LICENSE for more details. """ import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestExceptions(unittest.TestCase): +class TestExceptions(TestCase): def test_softlayer_api_error(self): e = SoftLayer.SoftLayerAPIError('fault code', 'fault string') @@ -32,7 +32,7 @@ def test_parse_error(self): str(e), "ParseError(fault code): fault string") -class TestUtils(unittest.TestCase): +class TestUtils(TestCase): def test_query_filter(self): result = SoftLayer.utils.query_filter('test') @@ -60,7 +60,7 @@ def test_query_filter(self): self.assertEqual({'operation': 10}, result) -class TestNestedDict(unittest.TestCase): +class TestNestedDict(TestCase): def test_basic(self): n = SoftLayer.utils.NestedDict() @@ -100,7 +100,7 @@ def test_to_dict(self): self.assertEqual(dict, type(d['test']['test1']['test2']['test3'])) -class TestLookup(unittest.TestCase): +class TestLookup(TestCase): def test_lookup(self): d = {'test': {'nested': 1}} @@ -131,9 +131,9 @@ class IdentifierFixture(SoftLayer.utils.IdentifierMixin): resolvers = [is_a, is_b] -class TestIdentifierMixin(unittest.TestCase): +class TestIdentifierMixin(TestCase): - def setUp(self): + def set_up(self): self.fixture = IdentifierFixture() def test_integer(self): diff --git a/SoftLayer/tests/config_tests.py b/SoftLayer/tests/config_tests.py index aacb2253b..c71316b0f 100644 --- a/SoftLayer/tests/config_tests.py +++ b/SoftLayer/tests/config_tests.py @@ -9,10 +9,10 @@ from SoftLayer.config import ( get_client_settings_args, get_client_settings_env, get_client_settings_config_file, get_client_settings) -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase -class TestGetClientSettings(unittest.TestCase): +class TestGetClientSettings(TestCase): @patch('SoftLayer.config.SETTING_RESOLVERS', []) def test_no_resolvers(self): @@ -40,7 +40,7 @@ def test_inherit(self): self.assertEqual(result, {'auth': 'AUTH HANDLER', 'timeout': 20}) -class TestGetClientSettingsArgs(unittest.TestCase): +class TestGetClientSettingsArgs(TestCase): def test_username_api_key(self): result = get_client_settings_args(username='username', @@ -74,7 +74,7 @@ def test_with_auth(self): self.assertEqual(result['auth'], auth) -class TestGetClientSettingsEnv(unittest.TestCase): +class TestGetClientSettingsEnv(TestCase): @patch.dict('os.environ', {'SL_USERNAME': 'username', 'SL_API_KEY': 'api_key', @@ -93,7 +93,7 @@ def test_no_auth(self): self.assertEqual(result, {'proxy': ANY}) -class TestGetClientSettingsConfigFile(unittest.TestCase): +class TestGetClientSettingsConfigFile(TestCase): @patch('six.moves.configparser.RawConfigParser') def test_username_api_key(self, config_parser): diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py index 16cc91d7d..afb54d67e 100644 --- a/SoftLayer/tests/functional_tests.py +++ b/SoftLayer/tests/functional_tests.py @@ -7,7 +7,7 @@ import os import SoftLayer -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase, unittest def get_creds(): @@ -24,7 +24,7 @@ def get_creds(): } -class UnauthedUser(unittest.TestCase): +class UnauthedUser(TestCase): def test_failed_auth(self): client = SoftLayer.Client( username='doesnotexist', api_key='issurelywrong', timeout=20) @@ -45,7 +45,7 @@ def test_no_hostname(self): self.fail('No Exception Raised') -class AuthedUser(unittest.TestCase): +class AuthedUser(TestCase): def test_service_does_not_exist(self): creds = get_creds() client = SoftLayer.Client( diff --git a/SoftLayer/tests/managers/cci_tests.py b/SoftLayer/tests/managers/cci_tests.py index 9772a7357..71bef617c 100644 --- a/SoftLayer/tests/managers/cci_tests.py +++ b/SoftLayer/tests/managers/cci_tests.py @@ -5,17 +5,17 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import CCIManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import ANY, call -class CCITests(unittest.TestCase): +class CCITests(TestCase): """ Small class to verify the rename of CCIManager to VSManager didn't break any existing code. """ - def setUp(self): + def set_up(self): self.client = FixtureClient() self.cci = CCIManager(self.client) diff --git a/SoftLayer/tests/managers/cdn_tests.py b/SoftLayer/tests/managers/cdn_tests.py index 64cf300fa..d2f403a9d 100644 --- a/SoftLayer/tests/managers/cdn_tests.py +++ b/SoftLayer/tests/managers/cdn_tests.py @@ -6,15 +6,15 @@ """ from SoftLayer.managers.cdn import (CDNManager, MAX_URLS_PER_LOAD, MAX_URLS_PER_PURGE) -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Account from mock import call from math import ceil -class CDNTests(unittest.TestCase): +class CDNTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.cdn_client = CDNManager(self.client) diff --git a/SoftLayer/tests/managers/dns_tests.py b/SoftLayer/tests/managers/dns_tests.py index 3d79c766c..23730a23f 100644 --- a/SoftLayer/tests/managers/dns_tests.py +++ b/SoftLayer/tests/managers/dns_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import DNSManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Dns_Domain, Account from mock import ANY -class DNSTests(unittest.TestCase): +class DNSTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.dns_client = DNSManager(self.client) diff --git a/SoftLayer/tests/managers/firewall_tests.py b/SoftLayer/tests/managers/firewall_tests.py index 1d079a15b..fa78a1afc 100644 --- a/SoftLayer/tests/managers/firewall_tests.py +++ b/SoftLayer/tests/managers/firewall_tests.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import FirewallManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import (Network_Component_Firewall, Network_Vlan_Firewall, Billing_Item) from mock import ANY @@ -16,9 +16,9 @@ 'sourceIpAddress,sourceIpSubnetMask,version]') -class FirewallTests(unittest.TestCase): +class FirewallTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.firewall = FirewallManager(self.client) @@ -120,8 +120,8 @@ def test_cancel_firewall(self): f.assert_called_once_with(id=billing_item_id) self.assertEqual(result, Billing_Item.cancelService) call = self.client['Network_Component_Firewall'].getObject - MASK = ('mask[id,billingItem[id]]') - call.assert_called_once_with(id=6327, mask=MASK) + mask = ('mask[id,billingItem[id]]') + call.assert_called_once_with(id=6327, mask=mask) # test dedicated firewalls billing_item_id = 21370815 result = self.firewall.cancel_firewall(fwl_id, dedicated=True) @@ -129,8 +129,8 @@ def test_cancel_firewall(self): f.assert_called_twice_with(id=billing_item_id) self.assertEqual(result, Billing_Item.cancelService) call = self.client['Network_Vlan_Firewall'].getObject - MASK = ('mask[id,billingItem[id]]') - call.assert_called_once_with(id=6327, mask=MASK) + mask = ('mask[id,billingItem[id]]') + call.assert_called_once_with(id=6327, mask=mask) def test_add_standard_firewall_cci(self): # test standard firewalls for CCI @@ -228,9 +228,9 @@ def test_edit_standard_fwl_rules(self): fwl_id = 1234 self.firewall.edit_standard_fwl_rules(firewall_id=fwl_id, rules=rules) - tempObject = { + temp_object = { "networkComponentFirewallId": fwl_id, "rules": rules} f = self.client['Network_Firewall_Update_Request'].createObject - f.assert_called_once_with(tempObject) + f.assert_called_once_with(temp_object) diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 3d57ea586..724e36f2f 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -6,16 +6,16 @@ """ from SoftLayer import HardwareManager from SoftLayer.managers.hardware import get_default_value -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import ( Hardware_Server, Account, Billing_Item, Ticket) from mock import ANY, call, patch -class HardwareTests(unittest.TestCase): +class HardwareTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.hardware = HardwareManager(self.client) diff --git a/SoftLayer/tests/managers/image_tests.py b/SoftLayer/tests/managers/image_tests.py index e191072ed..e386f6d2a 100644 --- a/SoftLayer/tests/managers/image_tests.py +++ b/SoftLayer/tests/managers/image_tests.py @@ -5,16 +5,16 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import ImageManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import ( Virtual_Guest_Block_Device_Template_Group, Account) from mock import ANY -class ImageTests(unittest.TestCase): +class ImageTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.image = ImageManager(self.client) self.vgbdtg = self.client['Virtual_Guest_Block_Device_Template_Group'] diff --git a/SoftLayer/tests/managers/iscsi_tests.py b/SoftLayer/tests/managers/iscsi_tests.py index 26c28c6c0..d4fac55a9 100644 --- a/SoftLayer/tests/managers/iscsi_tests.py +++ b/SoftLayer/tests/managers/iscsi_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import ISCSIManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Network_Storage_Iscsi from mock import ANY -class ISCSITests(unittest.TestCase): - def setUp(self): +class ISCSITests(TestCase): + def set_up(self): self.client = FixtureClient() self.iscsi = ISCSIManager(self.client) @@ -46,8 +46,8 @@ def test_invalid_datacenter(self): size=10, location='foo') def test_create_iscsi(self): - getItems = self.client['Product_Package'].getItems - getItems.return_value = [ + get_items = self.client['Product_Package'].getItems + get_items.return_value = [ { 'id': 4439, 'capacity': '1', @@ -77,8 +77,8 @@ def test_create_snapshot(self): f.assert_called_once_with('unNeeded', id=iscsi_id) def test_create_snapshot_space(self): - getItems = self.client['Product_Package'].getItems - getItems.return_value = [ + get_items = self.client['Product_Package'].getItems + get_items.return_value = [ { 'id': 1121, 'capacity': '20', diff --git a/SoftLayer/tests/managers/loadbal_tests.py b/SoftLayer/tests/managers/loadbal_tests.py index ff4874552..09e3c4663 100644 --- a/SoftLayer/tests/managers/loadbal_tests.py +++ b/SoftLayer/tests/managers/loadbal_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import LoadBalancerManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Billing_Item -class LoadBalancerTests(unittest.TestCase): +class LoadBalancerTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.lb_mgr = LoadBalancerManager(self.client) diff --git a/SoftLayer/tests/managers/metadata_tests.py b/SoftLayer/tests/managers/metadata_tests.py index da3b79472..506b90567 100644 --- a/SoftLayer/tests/managers/metadata_tests.py +++ b/SoftLayer/tests/managers/metadata_tests.py @@ -6,14 +6,14 @@ """ from SoftLayer import MetadataManager, SoftLayerError, SoftLayerAPIError from SoftLayer.consts import API_PRIVATE_ENDPOINT_REST, USER_AGENT -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from mock import patch, MagicMock -class MetadataTests(unittest.TestCase): +class MetadataTests(TestCase): - def setUp(self): + def set_up(self): self.metadata = MetadataManager() self.make_request = MagicMock() self.metadata.make_request = self.make_request @@ -73,9 +73,9 @@ def test_networks(self): }, r) -class MetadataTestsMakeRequest(unittest.TestCase): +class MetadataTestsMakeRequest(TestCase): - def setUp(self): + def set_up(self): self.metadata = MetadataManager() self.url = '/'.join([ API_PRIVATE_ENDPOINT_REST.rstrip('/'), diff --git a/SoftLayer/tests/managers/network_tests.py b/SoftLayer/tests/managers/network_tests.py index 5bc4d249e..1150934a5 100644 --- a/SoftLayer/tests/managers/network_tests.py +++ b/SoftLayer/tests/managers/network_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import NetworkManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Product_Order from mock import ANY, call -class NetworkTests(unittest.TestCase): +class NetworkTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.network = NetworkManager(self.client) diff --git a/SoftLayer/tests/managers/queue_tests.py b/SoftLayer/tests/managers/queue_tests.py index 5ed2db0ab..a10f6b4c6 100644 --- a/SoftLayer/tests/managers/queue_tests.py +++ b/SoftLayer/tests/managers/queue_tests.py @@ -6,7 +6,7 @@ """ from SoftLayer import MessagingManager, Unauthenticated, SoftLayerError from SoftLayer.consts import USER_AGENT -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase import SoftLayer.managers.messaging from mock import MagicMock, patch, ANY @@ -52,8 +52,8 @@ def mocked_auth_call(self): self.auth_token = 'NEW_AUTH_TOKEN' -class QueueAuthTests(unittest.TestCase): - def setUp(self): +class QueueAuthTests(TestCase): + def set_up(self): self.auth = SoftLayer.managers.messaging.QueueAuth( 'endpoint', 'username', 'api_key', auth_token='auth_token') @@ -119,9 +119,9 @@ def test_call_unauthed(self): self.assertEqual(request.headers, {'X-Auth-Token': 'NEW_AUTH_TOKEN'}) -class MessagingManagerTests(unittest.TestCase): +class MessagingManagerTests(TestCase): - def setUp(self): + def set_up(self): self.client = MagicMock() self.manager = MessagingManager(self.client) @@ -200,9 +200,9 @@ def test_ping(self, get): self.assertTrue(result) -class MessagingConnectionTests(unittest.TestCase): +class MessagingConnectionTests(TestCase): - def setUp(self): + def set_up(self): self.conn = SoftLayer.managers.messaging.MessagingConnection( 'acount_id', endpoint='endpoint') self.auth = MagicMock() diff --git a/SoftLayer/tests/managers/sshkey_tests.py b/SoftLayer/tests/managers/sshkey_tests.py index 633527f6c..4881e9d7f 100644 --- a/SoftLayer/tests/managers/sshkey_tests.py +++ b/SoftLayer/tests/managers/sshkey_tests.py @@ -5,13 +5,13 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import SshKeyManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import call -class SshKeyTests(unittest.TestCase): +class SshKeyTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.sshkey = SshKeyManager(self.client) diff --git a/SoftLayer/tests/managers/ssl_tests.py b/SoftLayer/tests/managers/ssl_tests.py index 12d9de7b0..d32478fe1 100644 --- a/SoftLayer/tests/managers/ssl_tests.py +++ b/SoftLayer/tests/managers/ssl_tests.py @@ -5,14 +5,14 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import SSLManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from mock import ANY -class SSLTests(unittest.TestCase): +class SSLTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ssl = SSLManager(self.client) self.test_id = 10 diff --git a/SoftLayer/tests/managers/ticket_tests.py b/SoftLayer/tests/managers/ticket_tests.py index 0799488b5..b1f67204d 100644 --- a/SoftLayer/tests/managers/ticket_tests.py +++ b/SoftLayer/tests/managers/ticket_tests.py @@ -5,14 +5,14 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import TicketManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Ticket from mock import ANY, call -class TicketTests(unittest.TestCase): +class TicketTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ticket = TicketManager(self.client) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 7d3eab461..b094b6810 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -5,15 +5,15 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import VSManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Virtual_Guest from mock import MagicMock, ANY, call, patch -class VSTests(unittest.TestCase): +class VSTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.vs = VSManager(self.client) @@ -534,11 +534,11 @@ def test_captures(self): def test_upgrade(self): # Testing Upgrade - orderClient = self.client['Product_Order'] + order_client = self.client['Product_Order'] # test single upgrade self.vs.upgrade(1, cpus=4, public=False) - orderClient.placeOrder.called_once_with(1, cpus=4, public=False) + order_client.placeOrder.called_once_with(1, cpus=4, public=False) # Now test a blank upgrade self.vs.upgrade(1) @@ -547,7 +547,7 @@ def test_upgrade(self): # Testing all parameters Upgrade self.vs.upgrade(1, cpus=4, memory=2, nic_speed=1000, public=True) args = {'cpus': 4, 'memory': 2, 'nic_speed': 1000, 'public': 1000} - orderClient.placeOrder.called_once_with(1, **args) + order_client.placeOrder.called_once_with(1, **args) def test_get_item_id_for_upgrade(self): item_id = 0 @@ -560,9 +560,9 @@ def test_get_item_id_for_upgrade(self): self.assertEqual(1133, item_id) -class VSWaitReadyGoTests(unittest.TestCase): +class VSWaitReadyGoTests(TestCase): - def setUp(self): + def set_up(self): self.client = MagicMock() self.vs = VSManager(self.client) self.guestObject = self.client['Virtual_Guest'].getObject diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py index 56bbbd742..535d12a98 100644 --- a/SoftLayer/tests/transport_tests.py +++ b/SoftLayer/tests/transport_tests.py @@ -8,13 +8,13 @@ from SoftLayer import SoftLayerAPIError, TransportError from SoftLayer.transports import make_rest_api_call, make_xml_rpc_api_call -from SoftLayer.tests import unittest +from SoftLayer.tests import TestCase from requests import HTTPError, RequestException -class TestXmlRpcAPICall(unittest.TestCase): +class TestXmlRpcAPICall(TestCase): - def setUp(self): + def set_up(self): self.send_content = ''' @@ -75,7 +75,7 @@ def test_valid_proxy(self, send): timeout=None) -class TestRestAPICall(unittest.TestCase): +class TestRestAPICall(TestCase): @patch('SoftLayer.transports.requests.request') def test_json(self, request): diff --git a/tox.ini b/tox.ini index 76bc4f943..f39c99810 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py33,py34,pypy,pep8,pylint +envlist = py26,py27,py33,py34,pypy,pep8,py2pep8 [testenv] deps = @@ -17,24 +17,22 @@ deps = -r{toxinidir}/tools/test-requirements.txt commands = {envpython} setup.py nosetests [] [testenv:pep8] -deps = flake8 -commands = flake8 \ - --max-complexity=36 \ - --statistics \ - SoftLayer - -[testenv:pylint] -deps = pylint -commands = pylint SoftLayer \ - --ignore=tests \ - -d R0903 \ # Too few public methods - -d R0914 \ # Too many local variables - -d R0201 \ # Method could be a function - -d I0011 \ # Locally Disabling - -d W0142 \ # Used * or ** magic - --max-args=20 \ - --max-branches=40 \ - --max-statements=86 \ - --max-module-lines=1200 \ - --max-returns=8 \ - --min-similarity-lines=50 # TODO: Remove \ No newline at end of file +deps = + flake8 + pep8-naming + pylint +commands = + flake8 --max-complexity=36 --statistics SoftLayer + pylint SoftLayer \ + --ignore=tests \ + -d R0903 \ # Too few public methods + -d R0914 \ # Too many local variables + -d R0201 \ # Method could be a function + -d I0011 \ # Locally Disabling + -d W0142 \ # Used * or ** magic + --max-args=20 \ + --max-branches=40 \ + --max-statements=86 \ + --max-module-lines=1200 \ + --max-returns=8 \ + --min-similarity-lines=50 # TODO: Remove From 3b7ea2131e10ca3d5944950da8195940dad0f2e5 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 22 May 2014 21:13:29 -0500 Subject: [PATCH 36/58] Remove py3 flake8 until the issues can be worked out --- .travis.yml | 1 - tox.ini | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a736003a4..36b5d012f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,5 @@ env: - TOX_ENV=py34 - TOX_ENV=pypy - TOX_ENV=pep8 - - TOX_ENV=py2pep8 install: pip install tox --use-mirrors script: tox -e $TOX_ENV diff --git a/tox.ini b/tox.ini index f39c99810..54378c7b5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] -envlist = py26,py27,py33,py34,pypy,pep8,py2pep8 - +envlist = py26,py27,py33,py34,pypy,pep8 [testenv] deps = unittest2 @@ -17,6 +16,7 @@ deps = -r{toxinidir}/tools/test-requirements.txt commands = {envpython} setup.py nosetests [] [testenv:pep8] +basepython = python2.7 deps = flake8 pep8-naming From d30667c3e475e41ee8cd75110eea28ee7b145caa Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 10:29:51 -0500 Subject: [PATCH 37/58] Fixes a couple errors in how the mask was being constructed for VSManager.get_instance --- SoftLayer/managers/vs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 8bcd9e184..296faf43f 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -163,9 +163,9 @@ def get_instance(self, instance_id, **kwargs): 'privateNetworkOnlyFlag', 'primaryBackendIpAddress', 'primaryIpAddress', - '''networkComponents[id, status, speed, maxSpeed, name,' + '''networkComponents[id, status, speed, maxSpeed, name, macAddress, primaryIpAddress, port, - primarySubnet]''' + primarySubnet]''', 'lastKnownPowerState.name', 'powerState', 'status', From effeaa2e971d82e8d09f8833234d03fe71d9a7f9 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 10:38:25 -0500 Subject: [PATCH 38/58] Fixes additional mask issue --- SoftLayer/managers/hardware.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index c294cb1c4..648999676 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -286,9 +286,12 @@ def get_hardware(self, hardware_id, **kwargs): networkIdentifier, gateway]]''', 'hardwareChassis[id,name]', 'activeTransaction[id, transactionStatus[friendlyName,name]]', - 'softwareDescription[manufacturer,name,version,referenceCode]', - '''operatingSystem[softwareLicense, - passwords[username,password]]''', + '''operatingSystem[ + softwareLicense[softwareDescription[manufacturer, + name, + version, + referenceCode]], + passwords[username,password]]''', 'billingItem.recurringFee', 'hourlyBillingFlag', 'tagReferences[id,tag[name,id]]', From af082e6219536ef4d2ca23e8e1369ca53da78d8c Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Fri, 23 May 2014 15:10:48 -0500 Subject: [PATCH 39/58] Config setup aborts after 3 attempts on each user input --- SoftLayer/CLI/modules/config.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 14a0e7cc5..5a8fe62b8 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -139,23 +139,27 @@ def get_user_input(self): timeout = defaults['timeout'] # Ask for username - while True: + for _ in range(3): username = self.env.input( 'Username [%s]: ' % defaults['username']) \ or defaults['username'] if username: break + else: + raise CLIAbort('Aborted after 3 attempts') # Ask for 'secret' which can be api_key or their password - while True: + for _ in range(3): secret = self.env.getpass( 'API Key or Password [%s]: ' % defaults['api_key']) \ or defaults['api_key'] if secret: break + else: + raise CLIAbort('Aborted after 3 attempts') # Ask for which endpoint they want to use - while True: + for _ in range(3): endpoint_type = self.env.input( 'Endpoint (public|private|custom): ') endpoint_type = endpoint_type.lower() @@ -173,6 +177,8 @@ def get_user_input(self): 'Endpoint URL [%s]: ' % defaults['endpoint_url'] ) or defaults['endpoint_url'] break + else: + raise CLIAbort('Aborted after 3 attempts') return username, secret, endpoint_url, timeout From 57691aaf65a74eeb55cd9cc1cbc8a4691d3a6db2 Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Mon, 26 May 2014 13:52:30 -0500 Subject: [PATCH 40/58] Ordering manager initial commit Tests, ordering manager logic and support for hardware manager --- SoftLayer/CLI/modules/server.py | 34 +++---- SoftLayer/managers/__init__.py | 4 +- SoftLayer/managers/hardware.py | 58 +++++------ SoftLayer/managers/ordering.py | 101 ++++++++++++++++++++ SoftLayer/tests/fixtures/Product_Package.py | 14 ++- SoftLayer/tests/managers/hardware_tests.py | 10 +- SoftLayer/tests/managers/ordering_tests.py | 42 ++++++++ 7 files changed, 203 insertions(+), 60 deletions(-) create mode 100644 SoftLayer/managers/ordering.py create mode 100644 SoftLayer/tests/managers/ordering_tests.py diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 59108e98b..8df9c69ff 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -30,14 +30,14 @@ CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager +from SoftLayer import HardwareManager, SshKeyManager, OrderingManager class ListServers(CLIRunnable): """ usage: sl server list [options] -List hardware servers on the acount +List hardware servers on the account Examples: sl server list --datacenter=dal05 @@ -63,7 +63,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client) + manager = HardwareManager(self.client, OrderingManager(self.client)) tags = None if args.get('--tags'): @@ -119,7 +119,7 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client) + hardware = HardwareManager(self.client, OrderingManager(self.client)) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -207,7 +207,7 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client) + hardware = HardwareManager(self.client, OrderingManager(self.client)) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] @@ -238,7 +238,7 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id( mgr.resolve_ids, args.get(''), 'hardware') @@ -269,7 +269,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -287,7 +287,7 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -312,7 +312,7 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -336,7 +336,7 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) @@ -352,7 +352,7 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -379,7 +379,7 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -399,7 +399,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -431,7 +431,7 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -785,7 +785,7 @@ class CreateServer(CLIRunnable): def execute(self, args): update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -845,7 +845,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) order = { 'hostname': args['--hostname'], @@ -1038,7 +1038,7 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client) + mgr = HardwareManager(self.client, OrderingManager(self.client)) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if not mgr.edit(hw_id, **data): diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py index b508483b3..6c7c6d702 100644 --- a/SoftLayer/managers/__init__.py +++ b/SoftLayer/managers/__init__.py @@ -22,7 +22,9 @@ from SoftLayer.managers.ticket import TicketManager from SoftLayer.managers.iscsi import ISCSIManager from SoftLayer.managers.vs import VSManager +from SoftLayer.managers.ordering import OrderingManager __all__ = ['CCIManager', 'DNSManager', 'FirewallManager', 'HardwareManager', 'ImageManager', 'MessagingManager', 'MetadataManager', 'CDNManager', 'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager', - 'VSManager', 'ISCSIManager', 'LoadBalancerManager'] + 'VSManager', 'ISCSIManager', 'LoadBalancerManager', + 'OrderingManager'] diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 648999676..8bf8b77d5 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -18,11 +18,12 @@ class HardwareManager(IdentifierMixin, object): :param SoftLayer.API.Client client: an API client instance """ - def __init__(self, client): + def __init__(self, client, ordering_manager): self.client = client self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + self.ordering_manager = ordering_manager def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): """ Cancels the specified dedicated server. @@ -185,17 +186,11 @@ def get_bare_metal_create_options(self): def get_bare_metal_package_id(self): """ Return the bare metal package id """ - packages = self.client['Product_Package'].getAllObjects( - mask='mask[id, name]', - filter={'name': query_filter('Bare Metal Instance')}) + ordering_manager = self.ordering_manager + mask = "mask[id,name,description,type[keyName]]" + package = ordering_manager.get_package_by_type('BARE_METAL_CORE', mask) - hw_id = 0 - for package in packages: - if 'Bare Metal Instance' == package['name']: - hw_id = package['id'] - break - - return hw_id + return package['id'] def get_available_dedicated_server_packages(self): """ Retrieves a list of packages that are available for ordering @@ -204,33 +199,26 @@ def get_available_dedicated_server_packages(self): :returns: A list of tuples of available dedicated server packages in the form (id, name, description) """ + available_packages = [] + ordering_manager = self.ordering_manager - package_obj = self.client['Product_Package'] - packages = [] - - # Pull back only server packages - mask = 'id,name,description,type' - _filter = { - 'type': { - 'keyName': { - 'operation': 'in', - 'options': [ - {'name': 'data', - 'value': ['BARE_METAL_CPU', 'BARE_METAL_CORE']} - ], - }, - }, - } + mask = 'id,name,description,type,isActive' + package_types = ['BARE_METAL_CPU', 'BARE_METAL_CORE'] - for package in package_obj.getAllObjects(mask=mask, filter=_filter): - # Filter out packages without a name or that are designated as - # 'OUTLET.' The outlet packages are missing some necessary data - # and their orders will fail. - if package.get('name') and 'OUTLET' not in package['description']: - packages.append((package['id'], package['name'], - package['description'])) + packages = ordering_manager.get_packages_of_type(package_types, + mask) + # We only want packages that are active (we can place new orders for) + # and non-outlet. + # Outlet packages require specialized logic and we don't want to deal + # with them right now. + packages = ordering_manager.get_only_active_packages(packages) + packages = ordering_manager.filter_outlet_packages(packages) + + for package in packages: + available_packages.append((package['id'], package['name'], + package['description'])) - return packages + return available_packages def get_dedicated_server_create_options(self, package_id): """ Retrieves the available options for creating a dedicated server in diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py new file mode 100644 index 000000000..f21c2b27a --- /dev/null +++ b/SoftLayer/managers/ordering.py @@ -0,0 +1,101 @@ +""" + SoftLayer.ordering + ~~~~~~~~~~~~~~~~~~ + Ordering Manager + + :license: MIT, see LICENSE for more details. +""" + + +class OrderingManager(object): + """ + Manages hardware devices. + + :param SoftLayer.API.Client client: an API client instance + """ + + def __init__(self, client): + self.client = client + + def get_packages_of_type(self, package_types, mask): + """ Get packages that match a certain type + + Each ordering package has a type, so return all packages that match + the types we are looking for + + :param list package_types: List of strings representing the package + type keynames we are interested in. + :param string mask: Mask to specify the properties we want to retrieve + """ + package_service = self.get_package_service() + _filter = { + 'type': { + 'keyName': { + 'operation': 'in', + 'options': [ + {'name': 'data', + 'value': package_types} + ], + }, + }, + } + + packages = package_service.getAllObjects(mask=mask, filter=_filter) + packages = self.filter_outlet_packages(packages) + return packages + + def get_package_service(self): + """ Get the service to query product packages + :return SoftLayer.API.Service + """ + return self.client['Product_Package'] + + @staticmethod + def filter_outlet_packages(packages): + """ Remove packages designated as OUTLET + + Those type of packages must be handled in a different way, + and they are not supported at the moment. + + :param packages: Dictionary of packages. Name and description keys + must be present in each of them. + """ + non_outlet_packages = [] + + for package in packages: + if 'OUTLET' not in package['description'].upper() and \ + 'OUTLET' not in package['name'].upper(): + non_outlet_packages.append(package) + + return non_outlet_packages + + + @staticmethod + def get_only_active_packages(packages): + """ Return only active packages + + If a package is active, it is eligible for ordering + This will inspect the 'isActive' property on the provided packages + + :param packages Dictionary of packages, isActive key must be present + """ + active_packages = [] + + for package in packages: + if package['isActive']: + active_packages.append(package) + + return active_packages + + def get_package_by_type(self, package_type, mask): + """ Get a single package of a given type. + + Syntactic sugar to retrieve a single package of a given type. + If multiple packages share the given type, this will return the first + one returned by the API. + + :param package_type string representing the package type key name + we are interested in + """ + packages = self.get_packages_of_type([package_type], mask) + return packages.pop() diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index ba9805095..6e7ef9107 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -1,9 +1,17 @@ getAllObjects = [ - {'id': 13, 'name': 'Mock Testing Package', 'description': 'a thing'}, + {'id': 13, 'name': 'Mock Testing Package', 'description': 'a thing', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 15, 'name': 'Inactive package', 'description': 'a cool server', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 0}, {'id': 27, 'name': 'An additional testing category', - 'description': 'Another thing - OUTLET'}, + 'description': 'Another thing - OUTLET', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 28, 'name': 'An outlet package', + 'description': 'Super fun package', + 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, {'id': 50, 'name': 'Bare Metal Instance', - 'description': 'Bare Metal Instance'}, + 'description': 'Bare Metal Instance', + 'type': {'keyName': 'BARE_METAL_CORE'}, 'isActive': 1}, ] getObject = getAllObjects[0] diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 3d57ea586..9770a95c5 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import HardwareManager +from SoftLayer import HardwareManager, OrderingManager from SoftLayer.managers.hardware import get_default_value from SoftLayer.tests import unittest, FixtureClient from SoftLayer.tests.fixtures import ( @@ -17,7 +17,8 @@ class HardwareTests(unittest.TestCase): def setUp(self): self.client = FixtureClient() - self.hardware = HardwareManager(self.client) + self.hardware = HardwareManager(self.client, + OrderingManager(self.client)) def test_list_hardware(self): mcall = call(mask=ANY, filter={}) @@ -94,7 +95,8 @@ def test_reload(self): def test_get_bare_metal_create_options_returns_none_on_error(self): self.client['Product_Package'].getAllObjects.return_value = [ - {'name': 'No Matching Instances', 'id': 0}] + {'name': 'No Matching Instances', 'id': 0, + 'description': 'Nothing'}] self.assertIsNone(self.hardware.get_bare_metal_create_options()) @@ -306,7 +308,7 @@ def test_get_available_dedicated_server_packages(self): } } f = self.client['Product_Package'].getAllObjects - f.assert_has_calls([call(mask='id,name,description,type', + f.assert_has_calls([call(mask='id,name,description,type,isActive', filter=filter_mock)]) def test_get_dedicated_server_options(self): diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py new file mode 100644 index 000000000..34aaeded5 --- /dev/null +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -0,0 +1,42 @@ +""" + SoftLayer.tests.managers.ordering_tests + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :license: MIT, see LICENSE for more details. +""" +from SoftLayer import OrderingManager +from SoftLayer.tests import unittest, FixtureClient + + +class OrderingTests(unittest.TestCase): + + def setUp(self): + self.client = FixtureClient() + self.ordering = OrderingManager(self.client) + + def test_get_package_by_type_returns_no_outlet_packages(self): + fixture_outlet_package_ids = [27, 28] + packages = self._get_server_packages() + filtered_packages = self.ordering.filter_outlet_packages(packages) + + for package_id in fixture_outlet_package_ids: + self._assert_package_id_not_present(package_id, filtered_packages) + + def _get_server_packages(self): + mask = 'id, name, description, type, isActive' + return self.ordering.get_packages_of_type(['BARE_METAL_CPU'], mask) + + def _assert_package_id_not_present(self, package_id, packages): + package_ids = [] + for package in packages: + package_ids.append(package['id']) + + self.assertNotIn(package_id, package_ids) + + def test_get_active_packages(self): + fixture_inactive_package_ids = [15] + packages = self._get_server_packages() + filtered_packages = self.ordering.get_only_active_packages(packages) + + for package_id in fixture_inactive_package_ids: + self._assert_package_id_not_present(package_id, filtered_packages) From c6ad9d146620659a83f74d805d37d8e240a9d26f Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Mon, 26 May 2014 14:02:29 -0500 Subject: [PATCH 41/58] Fix pylint issue --- SoftLayer/managers/ordering.py | 1 - 1 file changed, 1 deletion(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index f21c2b27a..c35846460 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -69,7 +69,6 @@ def filter_outlet_packages(packages): return non_outlet_packages - @staticmethod def get_only_active_packages(packages): """ Return only active packages From d30f57b30c6db25056c739887e19d35f8d1d073a Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Wed, 28 May 2014 19:57:30 -0500 Subject: [PATCH 42/58] Remove required parameter for ordering manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s keep it backwards compatible --- SoftLayer/CLI/modules/server.py | 32 +++++++++++----------- SoftLayer/managers/hardware.py | 13 +++++++-- SoftLayer/tests/managers/hardware_tests.py | 8 ++++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 8df9c69ff..b2e16852e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -30,7 +30,7 @@ CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager, OrderingManager +from SoftLayer import HardwareManager, SshKeyManager class ListServers(CLIRunnable): @@ -63,7 +63,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client, OrderingManager(self.client)) + manager = HardwareManager(self.client) tags = None if args.get('--tags'): @@ -119,7 +119,7 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client, OrderingManager(self.client)) + hardware = HardwareManager(self.client) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -207,7 +207,7 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client, OrderingManager(self.client)) + hardware = HardwareManager(self.client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] @@ -238,7 +238,7 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id( mgr.resolve_ids, args.get(''), 'hardware') @@ -269,7 +269,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -287,7 +287,7 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -312,7 +312,7 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if args['--really'] or confirm('This will power off the server with ' @@ -336,7 +336,7 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) @@ -352,7 +352,7 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -379,7 +379,7 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') @@ -399,7 +399,7 @@ def execute(self, args): table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -431,7 +431,7 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) table = KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' @@ -785,7 +785,7 @@ class CreateServer(CLIRunnable): def execute(self, args): update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -845,7 +845,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) order = { 'hostname': args['--hostname'], @@ -1038,7 +1038,7 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client, OrderingManager(self.client)) + mgr = HardwareManager(self.client) hw_id = resolve_id(mgr.resolve_ids, args.get(''), 'hardware') if not mgr.edit(hw_id, **data): diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 8bf8b77d5..9598d8ec3 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -9,6 +9,7 @@ # pylint: disable=C0103 import socket from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin +from SoftLayer.managers.ordering import OrderingManager class HardwareManager(IdentifierMixin, object): @@ -16,14 +17,20 @@ class HardwareManager(IdentifierMixin, object): Manages hardware devices. :param SoftLayer.API.Client client: an API client instance + :param SoftLayer.managers.OrderingManager ordering_manager: an optional + manager to handle ordering. + If none is provided, one will be + auto initialized. """ - - def __init__(self, client, ordering_manager): + def __init__(self, client, ordering_manager=None): self.client = client self.hardware = self.client['Hardware_Server'] self.account = self.client['Account'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] - self.ordering_manager = ordering_manager + if ordering_manager is None: + self.ordering_manager = OrderingManager(client) + else: + self.ordering_manager = ordering_manager def cancel_hardware(self, hardware_id, reason='unneeded', comment=''): """ Cancels the specified dedicated server. diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 9770a95c5..6fb183061 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -17,8 +17,7 @@ class HardwareTests(unittest.TestCase): def setUp(self): self.client = FixtureClient() - self.hardware = HardwareManager(self.client, - OrderingManager(self.client)) + self.hardware = HardwareManager(self.client) def test_list_hardware(self): mcall = call(mask=ANY, filter={}) @@ -311,6 +310,11 @@ def test_get_available_dedicated_server_packages(self): f.assert_has_calls([call(mask='id,name,description,type,isActive', filter=filter_mock)]) + def test_get_server_packages_with_ordering_manager_provided(self): + self.hardware = HardwareManager(self.client, + OrderingManager(self.client)) + self.test_get_available_dedicated_server_packages() + def test_get_dedicated_server_options(self): package_id = 13 self.hardware.get_dedicated_server_create_options(package_id) From e14c25fc9aff082ac779dae3a72fc54ccc0bb20a Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Sun, 1 Jun 2014 21:50:55 -0500 Subject: [PATCH 43/58] Fix pep8 naming for ordering manager To be in sync with changes from pull request #343 --- SoftLayer/tests/managers/ordering_tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index 34aaeded5..1c5385713 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -5,12 +5,12 @@ :license: MIT, see LICENSE for more details. """ from SoftLayer import OrderingManager -from SoftLayer.tests import unittest, FixtureClient +from SoftLayer.tests import TestCase, FixtureClient -class OrderingTests(unittest.TestCase): +class OrderingTests(TestCase): - def setUp(self): + def set_up(self): self.client = FixtureClient() self.ordering = OrderingManager(self.client) From 6f6ec6966903ff99085f76fb72f45180b2998bcc Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Tue, 3 Jun 2014 22:59:44 -0500 Subject: [PATCH 44/58] Remove hardcoded package ID from VS manager, and added more tests --- SoftLayer/managers/ordering.py | 20 +++++++++++++- SoftLayer/managers/vs.py | 23 +++++++++++++--- SoftLayer/tests/fixtures/Product_Package.py | 3 +++ SoftLayer/tests/managers/ordering_tests.py | 29 +++++++++++++++++++++ SoftLayer/tests/managers/vs_tests.py | 10 +++++-- 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py index c35846460..24484b3f8 100644 --- a/SoftLayer/managers/ordering.py +++ b/SoftLayer/managers/ordering.py @@ -92,9 +92,27 @@ def get_package_by_type(self, package_type, mask): Syntactic sugar to retrieve a single package of a given type. If multiple packages share the given type, this will return the first one returned by the API. + If no packages are found, returns None :param package_type string representing the package type key name we are interested in """ packages = self.get_packages_of_type([package_type], mask) - return packages.pop() + if len(packages) == 0: + return None + else: + return packages.pop() + + def get_package_id_by_type(self, package_type): + """ Return the package ID of a Product Package with a given type. + + :param package_type string representing the package type key name + we are interested in + :raises ValueError when no package of the given type is found + """ + mask = "mask[id, name, description, isActive, type[keyName]]" + package = self.get_package_by_type(package_type, mask) + if package: + return package['id'] + else: + raise ValueError("No package found for type: " + package_type) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 296faf43f..98912c553 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -11,15 +11,28 @@ from itertools import repeat from SoftLayer.utils import NestedDict, query_filter, IdentifierMixin, lookup +from SoftLayer.managers.ordering import OrderingManager class VSManager(IdentifierMixin, object): - """ Manage Virtual Servers """ - def __init__(self, client): + """ + Manages Virtual Servers + + :param SoftLayer.API.Client client: an API client instance + :param SoftLayer.managers.OrderingManager ordering_manager: an optional + manager to handle ordering. + If none is provided, one will be + auto initialized. + """ + def __init__(self, client, ordering_manager=None): self.client = client self.account = client['Account'] self.guest = client['Virtual_Guest'] self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname] + if ordering_manager is None: + self.ordering_manager = OrderingManager(client) + else: + self.ordering_manager = ordering_manager def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None, memory=None, hostname=None, domain=None, @@ -586,8 +599,10 @@ def _get_package_items(self): Following Method gets all the item ids related to VS """ mask = "mask[description,capacity,prices.id,categories[name,id]]" - package = self.client['Product_Package'] - return package.getItems(id=46, mask=mask) + package_type = "VIRTUAL_SERVER_INSTANCE" + package_id = self.ordering_manager.get_package_id_by_type(package_type) + return self.ordering_manager\ + .get_package_service().getItems(id=package_id, mask=mask) def _get_item_id_for_upgrade(self, package_items, option, value, public=True): diff --git a/SoftLayer/tests/fixtures/Product_Package.py b/SoftLayer/tests/fixtures/Product_Package.py index 6e7ef9107..d77f3f2af 100644 --- a/SoftLayer/tests/fixtures/Product_Package.py +++ b/SoftLayer/tests/fixtures/Product_Package.py @@ -9,6 +9,9 @@ {'id': 28, 'name': 'An outlet package', 'description': 'Super fun package', 'type': {'keyName': 'BARE_METAL_CPU'}, 'isActive': 1}, + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Bare Metal Instance', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, {'id': 50, 'name': 'Bare Metal Instance', 'description': 'Bare Metal Instance', 'type': {'keyName': 'BARE_METAL_CORE'}, 'isActive': 1}, diff --git a/SoftLayer/tests/managers/ordering_tests.py b/SoftLayer/tests/managers/ordering_tests.py index 1c5385713..7f55634ee 100644 --- a/SoftLayer/tests/managers/ordering_tests.py +++ b/SoftLayer/tests/managers/ordering_tests.py @@ -40,3 +40,32 @@ def test_get_active_packages(self): for package_id in fixture_inactive_package_ids: self._assert_package_id_not_present(package_id, filtered_packages) + + def test_get_package_by_type_returns_if_found(self): + package_type = "BARE_METAL_CORE" + mask = "mask[id, name]" + package = self.ordering.get_package_by_type(package_type, mask) + self.assertIsNotNone(package) + + def test_get_package_by_type_returns_none_if_not_found(self): + package_type = "PIZZA_FLAVORED_SERVERS" + mask = "mask[id, name]" + self.ordering.client['Product_Package'].getAllObjects.return_value = [] + package = self.ordering.get_package_by_type(package_type, mask) + self.assertIsNone(package) + + def test_get_package_id_by_type_returns_valid_id(self): + package_type = "VIRTUAL_SERVER_INSTANCE" + self.ordering.client['Product_Package'].getAllObjects.return_value = [ + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Virtual Server Instances', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, + ] + package_id = self.ordering.get_package_id_by_type(package_type) + self.assertEqual(46, package_id) + + def test_get_package_id_by_type_fails_for_nonexistent_package_type(self): + package_type = "STRAWBERRY_FLAVORED_SERVERS" + self.ordering.client['Product_Package'].getAllObjects.return_value = [] + with self.assertRaises(ValueError): + self.ordering.get_package_id_by_type(package_type) diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index b094b6810..e7a5f0508 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -4,7 +4,7 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer import VSManager +from SoftLayer import VSManager, OrderingManager from SoftLayer.tests import TestCase, FixtureClient from SoftLayer.tests.fixtures import Virtual_Guest @@ -15,7 +15,7 @@ class VSTests(TestCase): def set_up(self): self.client = FixtureClient() - self.vs = VSManager(self.client) + self.vs = VSManager(self.client, OrderingManager(self.client)) def test_list_instances(self): mcall = call(mask=ANY, filter={}) @@ -536,6 +536,12 @@ def test_upgrade(self): # Testing Upgrade order_client = self.client['Product_Order'] + self.client['Product_Package'].getAllObjects.return_value = [ + {'id': 46, 'name': 'Virtual Servers', + 'description': 'Virtual Server Instances', + 'type': {'keyName': 'VIRTUAL_SERVER_INSTANCE'}, 'isActive': 1}, + ] + # test single upgrade self.vs.upgrade(1, cpus=4, public=False) order_client.placeOrder.called_once_with(1, cpus=4, public=False) From 4a7de62957bc224ffccf640c02c6f360741da4ae Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Fri, 6 Jun 2014 18:10:16 -0500 Subject: [PATCH 45/58] Remove verifyOrder calls immediately followed by placeOrder Checked with order verification SME: placeOrder internally verifies the order, so no need to verify first if we intent to place the order, it will be verified automatically. --- SoftLayer/managers/iscsi.py | 2 -- SoftLayer/managers/vs.py | 1 - 2 files changed, 3 deletions(-) diff --git a/SoftLayer/managers/iscsi.py b/SoftLayer/managers/iscsi.py index 4ef19ca17..09fbb2c7f 100644 --- a/SoftLayer/managers/iscsi.py +++ b/SoftLayer/managers/iscsi.py @@ -66,7 +66,6 @@ def create_iscsi(self, size=None, location=None): item_price = self._find_item_prices(int(size), categorycode='iscsi') iscsi_order = self._build_order(item_price, location) - self.product_order.verifyOrder(iscsi_order) self.product_order.placeOrder(iscsi_order) def list_iscsi(self): @@ -147,7 +146,6 @@ def create_snapshot_space(self, volume_id, capacity): 'prices': [{'id': item_price}], 'quantity': 1, 'volumeId': volume_id} - self.product_order.verifyOrder(snapshotspaceorder) self.product_order.placeOrder(snapshotspaceorder) def delete_snapshot(self, snapshot_id): diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 98912c553..08b806306 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -589,7 +589,6 @@ def upgrade(self, instance_id, cpus=None, memory=None, order['properties'] = [{'name': 'MAINTENANCE_WINDOW', 'value': str(datetime.datetime.now())}] if cpus or memory or nic_speed: - self.client['Product_Order'].verifyOrder(order) self.client['Product_Order'].placeOrder(order) return True return False From 9f6624f450d4fdaabbd193ed344403292aa64255 Mon Sep 17 00:00:00 2001 From: sergiocarlos Date: Fri, 13 Jun 2014 23:14:02 -0500 Subject: [PATCH 46/58] Minor change to improve code readability --- SoftLayer/managers/vs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 08b806306..039d9460a 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -600,8 +600,9 @@ def _get_package_items(self): mask = "mask[description,capacity,prices.id,categories[name,id]]" package_type = "VIRTUAL_SERVER_INSTANCE" package_id = self.ordering_manager.get_package_id_by_type(package_type) - return self.ordering_manager\ - .get_package_service().getItems(id=package_id, mask=mask) + package_service = self.ordering_manager.get_package_service() + + return package_service.getItems(id=package_id, mask=mask) def _get_item_id_for_upgrade(self, package_items, option, value, public=True): From 3f453f503902f5624e8df99e416827ae637e9f71 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 18 Jun 2014 16:33:30 -0500 Subject: [PATCH 47/58] Bumps required six version --- setup.py | 2 +- tools/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 5151bc4a5..e49ee3b65 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ extra = {} requires = [ - 'six >= 1.1.0', + 'six >= 1.7.0', 'prettytable >= 0.7.0', 'docopt == 0.6.1', 'requests', diff --git a/tools/requirements.txt b/tools/requirements.txt index a20851709..ef915c8bb 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,4 +1,4 @@ requests prettytable >= 0.7.0 docopt == 0.6.1 -six >= 1.1.0 +six >= 1.7.0 From d90770d185c28742ee750a732aa3749ff58ff158 Mon Sep 17 00:00:00 2001 From: underscorephil Date: Tue, 8 Jul 2014 12:46:47 -0500 Subject: [PATCH 48/58] Add contrib info and CLAs --- CONTRIBUTING.md | 17 ++++++ docs/cla-corporate.md | 134 +++++++++++++++++++++++++++++++++++++++++ docs/cla-individual.md | 85 ++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 docs/cla-corporate.md create mode 100644 docs/cla-individual.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1d18c35ec --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,17 @@ +# Contributing to softlayer-python + +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/cla-individual.md). + +* 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/cla-corporate.md). + +* Fork the repo, make your changes, and open a pull request. + +* Additional infomration can be found in our [contribution guide](http://softlayer-python.readthedocs.org/en/latest/dev/index.html) + + diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md new file mode 100644 index 000000000..7a939f0b2 --- /dev/null +++ b/docs/cla-corporate.md @@ -0,0 +1,134 @@ +#### International Business machines, Inc. +#####Software Grant and Corporate Contributor License Agreement ("Agreement") + +http://www.github.org/softlayer/softlayer-python/ + + +Thank you for your interest in IBM’s softlayer-python project (“the +Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms +below. This license is for your protection as a Contributor as well +as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions +submitted by its designated employees to the Project, and to grant +copyright and patent licenses thereto + +If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com. + + + +Please read this document carefully before signing and keep a copy for your records. + +Corporation name: ________________________________________________ + +Corporation address: ________________________________________________ + +Point of Contact: ________________________________________________ + +E-Mail: ________________________________________________ + +Telephone: _____________________ + + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to the Project. Except +for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with IBM. For legal entities, the entity making a Contribution and + all other entities that control, are controlled by, or are under + common control with that entity are considered to be a single + Contributor. For the purposes of this definition, "control" means + (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, or + (ii) ownership of fifty percent (50%) or more of the outstanding + shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original + works of authorship expressly identified in Schedule B, as well as + any original work of authorship, including any modifications or +additions to an existing work, that is intentionally submitted by You to IBM for inclusion in, or documentation of, the Project managed by IBM (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to IBM or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, IBM for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions + of this Agreement, You hereby grant to IBM and to + recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute + Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to IBM and to recipients + of software distributed by IBM a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as + stated in this section) patent license to make, have made, use, + offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by You that are necessarily infringed by Your Contribution(s) + alone or by combination of Your Contribution(s) with the Work to + which such Contribution(s) were submitted. If any entity institutes + patent litigation against You or any other entity (including a + cross-claim or counterclaim in a lawsuit) alleging that your + Contribution, or the Work to which you have contributed, constitutes + direct or contributory patent infringement, then any patent licenses + granted to that entity under this Agreement for that Contribution or + Work shall terminate as of the date such litigation is filed. + +4. You represent that You are legally entitled to grant the above + license. You represent further that each employee of the + Corporation designated on Schedule A below (or in a subsequent + written modification to that Schedule) is authorized to submit + Contributions on behalf of the Corporation. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, + MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to IBM separately from any + Contribution, identifying the complete details of its source and + of any license or other restriction (including, but not limited + to, related patents, trademarks, and license agreements) of which + you are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. It is your responsibility to notify IBM when any change + is required to the list of designated employees authorized to submit + Contributions on behalf of the Corporation, or to the Corporation's + Point of Contact with IBM. + + + +Please sign: __________________________________ Date: _______________ + +Title: __________________________________ + +Corporation: __________________________________ + + +Schedule A + + [Initial list of designated employees. NB: authorization is not + tied to particular Contributions.] + + + + +Schedule B + + [Identification of optional concurrent software grant. Would be + left blank or omitted if there is no concurrent software grant.] + + + diff --git a/docs/cla-individual.md b/docs/cla-individual.md new file mode 100644 index 000000000..95e6299ed --- /dev/null +++ b/docs/cla-individual.md @@ -0,0 +1,85 @@ +#### International Business Machines, Inc. (IBM) +#####Individual Contributor License Agreement ("Agreement") + +http://www.github.com/softlayer/softlayer-python + +Thank you for your interest in the softlayer-python project ("the Project"). + +In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement ("CLA") on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its customers; it does not change your rights to use your own Contributions for any other purpose. + +If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com + +Please read this document carefully before signing and keep a copy for your records. + + Full name: ______________________________________________________ + + (optional) Public name: _________________________________________ + + Mailing Address: ________________________________________________ + + Country: ______________________________________________________ + + Telephone: ______________________________________________________ + + E-Mail: ______________________________________________________ + + +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement +with IBM. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to the Project for inclusion +in, or documentation of, the Project (”the Work”). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives,including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of +this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of +this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work to which Your Contribution(s) were submitted, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to the Project, or that your employer has + executed a separate Corporate CLA with IBM. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to the Project separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify IBM of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Please sign: __________________________________ Date: ________________ + + From 6385978bfa30b74c5b9bb77ba6ad3e5f0fbd36c6 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 12:49:28 -0500 Subject: [PATCH 49/58] Update CONTRIBUTING.md --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d18c35ec..8b54b2cc4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,12 +3,10 @@ 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/cla-individual.md). +* Sign our contributor agreement (CLA) You can find the [CLA here](./docs/cla-individual.md). * 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/cla-corporate.md). +* our corporate contributor agreement (CCLA) as well. You can find the [CCLA here](./docs/cla-corporate.md). * Fork the repo, make your changes, and open a pull request. From a1bda439171e42cfbc8ba40f27ca9446a08975c3 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 12:57:20 -0500 Subject: [PATCH 50/58] Update CONTRIBUTING.md format fixes --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b54b2cc4..7a82eb151 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,8 +5,7 @@ guidelines below. * Sign our contributor agreement (CLA) You can find the [CLA here](./docs/cla-individual.md). -* 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/cla-corporate.md). +* 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/cla-corporate.md). * Fork the repo, make your changes, and open a pull request. From 6d77c894698123d3b68c133113f5302041b073d5 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:50:33 -0500 Subject: [PATCH 51/58] Update cla-corporate.md line wrapping fixes --- docs/cla-corporate.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index 7a939f0b2..776162c05 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -4,15 +4,10 @@ http://www.github.org/softlayer/softlayer-python/ -Thank you for your interest in IBM’s softlayer-python project (“the -Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms -below. This license is for your protection as a Contributor as well -as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. - -This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions -submitted by its designated employees to the Project, and to grant -copyright and patent licenses thereto - +Thank you for your interest in IBM’s softlayer-python project (“the Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. + +This version of the Agreement allows an entity (the "Corporation") to submit Contributions to the Project, to authorize Contributions submitted by its designated employees to the Project, and to grant copyright and patent licenses thereto. + If you have not already done so, please complete and sign, then scan and email a pdf file of this Agreement to pjackson@softlayer.com. @@ -30,9 +25,7 @@ E-Mail: ________________________________________________ Telephone: _____________________ -You accept and agree to the following terms and conditions for Your -present and future Contributions submitted to the Project. Except -for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. +You accept and agree to the following terms and conditions for Your present and future Contributions submitted to the Project. Except for the license granted herein to IBM and recipients of software distributed by IBM, You reserve all right, title, and interest in and to Your Contributions. 1. Definitions. @@ -50,7 +43,16 @@ for the license granted herein to IBM and recipients of software distributed by "Contribution" shall mean the code, documentation or other original works of authorship expressly identified in Schedule B, as well as any original work of authorship, including any modifications or -additions to an existing work, that is intentionally submitted by You to IBM for inclusion in, or documentation of, the Project managed by IBM (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to IBM or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, IBM for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." + additions to an existing work, that is intentionally submitted by + You to IBM for inclusion in, or documentation of, the Project managed + by IBM (the "Work"). For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent to + IBM or its representatives, including but not limited to communication + on electronic mailing lists, source code control systems, and issue + tracking systems that are managed by, or on behalf of, IBM for the + purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated + in writing by You as "Not a Contribution." 2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to IBM and to From ae072bcfafdc2ba21f0d44b267e8fdc0e7706679 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:55:19 -0500 Subject: [PATCH 52/58] Update cla-individual.md line wrapping fixes --- docs/cla-individual.md | 62 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/docs/cla-individual.md b/docs/cla-individual.md index 95e6299ed..86a4fff44 100644 --- a/docs/cla-individual.md +++ b/docs/cla-individual.md @@ -30,21 +30,58 @@ You accept and agree to the following terms and conditions for Your present and "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement -with IBM. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + with IBM. For legal entities, the entity making a Contribution and + all other entities that control, are controlled by, or are under + common control with that entity are considered to be a single + Contributor. For the purposes of this definition, "control" means + (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, + or (ii) ownership of fifty percent (50%) or more of the outstanding + shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to the Project for inclusion -in, or documentation of, the Project (”the Work”). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Project or its representatives,including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." - -2. Grant of Copyright License. Subject to the terms and conditions of -this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of -this Agreement, You hereby grant to IBM and to recipients of software distributed by IBM a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work to which Your Contribution(s) were submitted, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + in, or documentation of, the Project (”the Work”). For the purposes + of this definition, "submitted" means any form of electronic, verbal, + or written communication sent to the Project or its representatives, + including but not limited to communication on electronic mailing lists, + source code control systems, and issue tracking systems that are + managed by, or on behalf of, the Project for the purpose of discussing + and improving the Work, but excluding communication that is conspicuously + marked or otherwise designated in writing by You as "Not a Contribution." + +2. Grant of Copyright License. + + Subject to the terms and conditions of this Agreement, You hereby grant + to IBM and to recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright + license to reproduce, prepare derivative works of, publicly display, + publicly perform, sublicense, and distribute Your Contributions and + such derivative works. + +3. Grant of Patent License. + + Subject to the terms and conditions of this Agreement, You hereby grant + to IBM and to recipients of software distributed by IBM a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except + as stated in this section) patent license to make, have made, use, offer + to sell, sell, import, and otherwise transfer the Work to which Your + Contribution(s) were submitted, where such license applies only to those + patent claims licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) with the + Work to which such Contribution(s) was submitted. If any entity institutes + patent litigation against You or any other entity (including a cross-claim + or counterclaim in a lawsuit) alleging that your Contribution, or the Work + to which you have contributed, constitutes direct or contributory patent + infringement, then any patent licenses granted to that entity under this + Agreement for that Contribution or Work shall terminate as of the date + such litigation is filed. 4. You represent that you are legally entitled to grant the above - license. If your employer(s) has rights to intellectual property + license. + + If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for @@ -60,9 +97,10 @@ this Agreement, You hereby grant to IBM and to recipients of software distribute Contributions. 6. You are not expected to provide support for Your Contributions, - except to the extent You desire to provide support. You may provide - support for free, for a fee, or not at all. Unless required by - applicable law or agreed to in writing, You provide Your + except to the extent You desire to provide support. + + You may provide support for free, for a fee, or not at all. + Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- From 16b92c4d07933b876c0a0c9d5cd4111bea0e3408 Mon Sep 17 00:00:00 2001 From: Phil Date: Tue, 8 Jul 2014 13:57:10 -0500 Subject: [PATCH 53/58] Update cla-corporate.md additional readability fixes --- docs/cla-corporate.md | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index 776162c05..cc5e35ffd 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -54,18 +54,22 @@ You accept and agree to the following terms and conditions for Your present and communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." -2. Grant of Copyright License. Subject to the terms and conditions - of this Agreement, You hereby grant to IBM and to - recipients of software distributed by IBM a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare derivative works of, - publicly display, publicly perform, sublicense, and distribute - Your Contributions and such derivative works. - -3. Grant of Patent License. Subject to the terms and conditions of - this Agreement, You hereby grant to IBM and to recipients - of software distributed by IBM a perpetual, worldwide, - non-exclusive, no-charge, royalty-free, irrevocable (except as +2. Grant of Copyright License. + + Subject to the terms and conditions of this Agreement, + You hereby grant to IBM and to recipients of software + distributed by IBM a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable copyright license to + reproduce, prepare derivative works of, publicly display, + publicly perform, sublicense, and distribute Your Contributions + and such derivative works. + +3. Grant of Patent License. + + Subject to the terms and conditions of this Agreement, + You hereby grant to IBM and to recipients of software + distributed by IBM a perpetual, worldwide, non-exclusive, + no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable @@ -80,9 +84,11 @@ You accept and agree to the following terms and conditions for Your present and Work shall terminate as of the date such litigation is filed. 4. You represent that You are legally entitled to grant the above - license. You represent further that each employee of the - Corporation designated on Schedule A below (or in a subsequent - written modification to that Schedule) is authorized to submit + license. + + You represent further that each employee of the Corporation + designated on Schedule A below (or in a subsequent written + modification to that Schedule) is authorized to submit Contributions on behalf of the Corporation. 5. You represent that each of Your Contributions is Your original From 489fb7956567fd5d13a9a5c73ddb6d67210e6d0b Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 14:20:34 -0500 Subject: [PATCH 54/58] Fixes incorrect URL --- docs/cla-corporate.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cla-corporate.md b/docs/cla-corporate.md index cc5e35ffd..9285bc432 100644 --- a/docs/cla-corporate.md +++ b/docs/cla-corporate.md @@ -1,7 +1,7 @@ #### International Business machines, Inc. #####Software Grant and Corporate Contributor License Agreement ("Agreement") -http://www.github.org/softlayer/softlayer-python/ +http://www.github.com/softlayer/softlayer-python/ Thank you for your interest in IBM’s softlayer-python project (“the Project"). In order to clarify the intellectual property license granted with Contributions from any person or entity, IBM must have a Contributor License Agreement (CLA) on file that has been signed by each Contributor, indicating agreement to the license terms below. This license is for your protection as a Contributor as well as the protection of IBM and its users; it does not change your rights to use your own Contributions for any other purpose. From 8bbd902bbdceeb2f216bef3d72eb332faed804c6 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 22:17:38 -0500 Subject: [PATCH 55/58] Adds hacking to pep8 tox tests. Fixes many errors --- SoftLayer/API.py | 74 ++-- SoftLayer/CLI/core.py | 72 ++-- SoftLayer/CLI/environment.py | 72 ++-- SoftLayer/CLI/exceptions.py | 25 +- SoftLayer/CLI/formatting.py | 104 +++-- SoftLayer/CLI/helpers.py | 32 +- SoftLayer/CLI/modules/__init__.py | 6 +- SoftLayer/CLI/modules/cdn.py | 50 ++- SoftLayer/CLI/modules/config.py | 71 ++-- SoftLayer/CLI/modules/dns.py | 70 ++-- SoftLayer/CLI/modules/firewall.py | 99 ++--- SoftLayer/CLI/modules/globalip.py | 61 +-- SoftLayer/CLI/modules/help.py | 8 +- SoftLayer/CLI/modules/image.py | 62 +-- SoftLayer/CLI/modules/iscsi.py | 63 +-- SoftLayer/CLI/modules/loadbal.py | 149 +++---- SoftLayer/CLI/modules/messaging.py | 103 ++--- SoftLayer/CLI/modules/metadata.py | 64 +-- SoftLayer/CLI/modules/nas.py | 31 +- SoftLayer/CLI/modules/rwhois.py | 20 +- SoftLayer/CLI/modules/server.py | 268 +++++++------ SoftLayer/CLI/modules/snapshot.py | 79 ++-- SoftLayer/CLI/modules/sshkey.py | 59 +-- SoftLayer/CLI/modules/ssl.py | 48 ++- SoftLayer/CLI/modules/subnet.py | 81 ++-- SoftLayer/CLI/modules/summary.py | 11 +- SoftLayer/CLI/modules/ticket.py | 62 +-- SoftLayer/CLI/modules/vlan.py | 34 +- SoftLayer/CLI/modules/vs.py | 376 ++++++++++-------- SoftLayer/CLI/template.py | 14 +- SoftLayer/__init__.py | 12 +- SoftLayer/auth.py | 13 +- SoftLayer/config.py | 28 +- SoftLayer/exceptions.py | 41 +- SoftLayer/managers/__init__.py | 35 +- SoftLayer/managers/cci.py | 5 +- SoftLayer/managers/cdn.py | 13 +- SoftLayer/managers/dns.py | 20 +- SoftLayer/managers/firewall.py | 18 +- SoftLayer/managers/hardware.py | 46 +-- SoftLayer/managers/image.py | 20 +- SoftLayer/managers/iscsi.py | 4 +- SoftLayer/managers/load_balancer.py | 40 +- SoftLayer/managers/messaging.py | 19 +- SoftLayer/managers/metadata.py | 21 +- SoftLayer/managers/network.py | 46 ++- SoftLayer/managers/ordering.py | 4 +- SoftLayer/managers/sshkey.py | 8 +- SoftLayer/managers/ticket.py | 4 +- SoftLayer/managers/vs.py | 70 ++-- SoftLayer/testing/__init__.py | 38 ++ .../{tests => testing}/fixture_client.py | 25 +- .../{tests => testing}/fixtures/Account.py | 3 - .../fixtures/Billing_Item.py | 0 .../{tests => testing}/fixtures/Dns_Domain.py | 0 .../fixtures/Dns_Domain_ResourceRecord.py | 0 .../fixtures/Hardware_Server.py | 0 .../{tests => testing}/fixtures/Location.py | 0 .../fixtures/Location_Datacenter.py | 0 ...ntroller_LoadBalancer_Health_Check_Type.py | 0 ..._Controller_LoadBalancer_Routing_Method.py | 0 ...ry_Controller_LoadBalancer_Routing_Type.py | 0 ...elivery_Controller_LoadBalancer_Service.py | 0 ...y_Controller_LoadBalancer_Service_Group.py | 0 ...ontroller_LoadBalancer_VirtualIpAddress.py | 0 ...y_Controller_LoadBalancer_VirtualServer.py | 0 .../fixtures/Network_Component_Firewall.py | 0 .../Network_ContentDelivery_Account.py | 0 .../Network_Firewall_Update_Request.py | 4 +- .../fixtures/Network_Storage_Iscsi.py | 14 +- .../fixtures/Network_Subnet.py | 0 .../fixtures/Network_Subnet_IpAddress.py | 0 .../Network_Subnet_IpAddress_Global.py | 0 .../fixtures/Network_Subnet_Rwhois_Data.py | 0 .../fixtures/Network_Vlan.py | 0 .../fixtures/Network_Vlan_Firewall.py | 4 +- .../fixtures/Product_Order.py | 0 .../fixtures/Product_Package.py | 46 +-- .../fixtures/Security_Certificate.py | 0 .../fixtures/Security_Ssh_Key.py | 0 .../{tests => testing}/fixtures/Ticket.py | 0 .../fixtures/Ticket_Subject.py | 0 .../fixtures/User_Customer.py | 0 .../fixtures/Virtual_Guest.py | 4 +- ...rtual_Guest_Block_Device_Template_Group.py | 0 .../{tests => testing}/fixtures/__init__.py | 0 .../{tests => testing}/fixtures/empty.conf | 0 .../{tests => testing}/fixtures/full.conf | 0 .../{tests => testing}/fixtures/id_rsa.pub | 0 .../fixtures/no_options.conf | 0 .../fixtures/sample_vs_template.conf | 0 SoftLayer/tests/CLI/core_tests.py | 39 +- SoftLayer/tests/CLI/environment_tests.py | 25 +- SoftLayer/tests/CLI/helper_tests.py | 225 ++++++----- SoftLayer/tests/CLI/modules/cdn_tests.py | 14 +- SoftLayer/tests/CLI/modules/config_tests.py | 39 +- SoftLayer/tests/CLI/modules/dns_tests.py | 41 +- SoftLayer/tests/CLI/modules/firewall_tests.py | 11 +- SoftLayer/tests/CLI/modules/globalip_tests.py | 20 +- SoftLayer/tests/CLI/modules/help_tests.py | 8 +- SoftLayer/tests/CLI/modules/import_tests.py | 14 +- SoftLayer/tests/CLI/modules/nas_tests.py | 10 +- SoftLayer/tests/CLI/modules/rwhois_tests.py | 14 +- SoftLayer/tests/CLI/modules/server_tests.py | 170 ++++---- SoftLayer/tests/CLI/modules/sshkey_tests.py | 28 +- SoftLayer/tests/CLI/modules/summary_tests.py | 15 +- SoftLayer/tests/CLI/modules/vs_tests.py | 20 +- SoftLayer/tests/__init__.py | 31 -- SoftLayer/tests/api_tests.py | 122 +++--- SoftLayer/tests/auth_tests.py | 31 +- SoftLayer/tests/basic_tests.py | 32 +- SoftLayer/tests/config_tests.py | 79 ++-- SoftLayer/tests/functional_tests.py | 10 +- SoftLayer/tests/managers/cci_tests.py | 14 +- SoftLayer/tests/managers/cdn_tests.py | 39 +- SoftLayer/tests/managers/dns_tests.py | 24 +- SoftLayer/tests/managers/firewall_tests.py | 46 +-- SoftLayer/tests/managers/hardware_tests.py | 75 ++-- SoftLayer/tests/managers/image_tests.py | 54 +-- SoftLayer/tests/managers/iscsi_tests.py | 23 +- SoftLayer/tests/managers/loadbal_tests.py | 18 +- SoftLayer/tests/managers/metadata_tests.py | 41 +- SoftLayer/tests/managers/network_tests.py | 49 +-- SoftLayer/tests/managers/ordering_tests.py | 10 +- SoftLayer/tests/managers/queue_tests.py | 130 +++--- SoftLayer/tests/managers/sshkey_tests.py | 21 +- SoftLayer/tests/managers/ssl_tests.py | 18 +- SoftLayer/tests/managers/ticket_tests.py | 21 +- SoftLayer/tests/managers/vs_tests.py | 73 ++-- SoftLayer/tests/transport_tests.py | 86 ++-- SoftLayer/transports.py | 60 +-- SoftLayer/utils.py | 42 +- setup.py | 1 + tox.ini | 18 +- 134 files changed, 2487 insertions(+), 2247 deletions(-) create mode 100644 SoftLayer/testing/__init__.py rename SoftLayer/{tests => testing}/fixture_client.py (66%) rename SoftLayer/{tests => testing}/fixtures/Account.py (98%) rename SoftLayer/{tests => testing}/fixtures/Billing_Item.py (100%) rename SoftLayer/{tests => testing}/fixtures/Dns_Domain.py (100%) rename SoftLayer/{tests => testing}/fixtures/Dns_Domain_ResourceRecord.py (100%) rename SoftLayer/{tests => testing}/fixtures/Hardware_Server.py (100%) rename SoftLayer/{tests => testing}/fixtures/Location.py (100%) rename SoftLayer/{tests => testing}/fixtures/Location_Datacenter.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Health_Check_Type.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Routing_Method.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Routing_Type.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Service.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_Service_Group.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_VirtualIpAddress.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Application_Delivery_Controller_LoadBalancer_VirtualServer.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Component_Firewall.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_ContentDelivery_Account.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Firewall_Update_Request.py (80%) rename SoftLayer/{tests => testing}/fixtures/Network_Storage_Iscsi.py (81%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_IpAddress.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_IpAddress_Global.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Subnet_Rwhois_Data.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Vlan.py (100%) rename SoftLayer/{tests => testing}/fixtures/Network_Vlan_Firewall.py (98%) rename SoftLayer/{tests => testing}/fixtures/Product_Order.py (100%) rename SoftLayer/{tests => testing}/fixtures/Product_Package.py (96%) rename SoftLayer/{tests => testing}/fixtures/Security_Certificate.py (100%) rename SoftLayer/{tests => testing}/fixtures/Security_Ssh_Key.py (100%) rename SoftLayer/{tests => testing}/fixtures/Ticket.py (100%) rename SoftLayer/{tests => testing}/fixtures/Ticket_Subject.py (100%) rename SoftLayer/{tests => testing}/fixtures/User_Customer.py (100%) rename SoftLayer/{tests => testing}/fixtures/Virtual_Guest.py (98%) rename SoftLayer/{tests => testing}/fixtures/Virtual_Guest_Block_Device_Template_Group.py (100%) rename SoftLayer/{tests => testing}/fixtures/__init__.py (100%) rename SoftLayer/{tests => testing}/fixtures/empty.conf (100%) rename SoftLayer/{tests => testing}/fixtures/full.conf (100%) rename SoftLayer/{tests => testing}/fixtures/id_rsa.pub (100%) rename SoftLayer/{tests => testing}/fixtures/no_options.conf (100%) rename SoftLayer/{tests => testing}/fixtures/sample_vs_template.conf (100%) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 5bd2fdd28..0f1952882 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -7,12 +7,13 @@ """ import time -from .consts import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT, USER_AGENT -from .transports import make_xml_rpc_api_call -from .auth import TokenAuthentication -from .config import get_client_settings - +from SoftLayer import auth as slauth +from SoftLayer import config +from SoftLayer import consts +from SoftLayer import transports +API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT +API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT __all__ = ['Client', 'TimedClient', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT'] @@ -29,7 +30,7 @@ class Client(object): - """ A SoftLayer API client. + """A SoftLayer API client. :param username: an optional API username if you wish to bypass the package's built-in username @@ -58,13 +59,13 @@ class Client(object): def __init__(self, username=None, api_key=None, endpoint_url=None, timeout=None, auth=None, config_file=None, proxy=None): - settings = get_client_settings(username=username, - api_key=api_key, - endpoint_url=endpoint_url, - timeout=timeout, - auth=auth, - proxy=proxy, - config_file=config_file) + settings = config.get_client_settings(username=username, + api_key=api_key, + endpoint_url=endpoint_url, + timeout=timeout, + auth=auth, + proxy=proxy, + config_file=config_file) self.auth = settings.get('auth') self.endpoint_url = ( settings.get('endpoint_url') or API_PUBLIC_ENDPOINT).rstrip('/') @@ -78,8 +79,7 @@ def __init__(self, username=None, api_key=None, endpoint_url=None, def authenticate_with_password(self, username, password, security_question_id=None, security_question_answer=None): - """ Performs Username/Password Authentication and gives back an auth - handler to use to create a client that uses token-based auth. + """Performs Username/Password Authentication :param string username: your SoftLayer username :param string password: your SoftLayer password @@ -94,11 +94,11 @@ def authenticate_with_password(self, username, password, password, security_question_id, security_question_answer) - self.auth = TokenAuthentication(res['userId'], res['hash']) + self.auth = slauth.TokenAuthentication(res['userId'], res['hash']) return res['userId'], res['hash'] def __getitem__(self, name): - """ Get a SoftLayer Service. + """Get a SoftLayer Service. :param name: The name of the service. E.G. Account @@ -112,7 +112,7 @@ def __getitem__(self, name): return Service(self, name) def call(self, service, method, *args, **kwargs): - """ Make a SoftLayer API call + """Make a SoftLayer API call :param service: the name of the SoftLayer API service :param method: the method to call on the service @@ -162,7 +162,7 @@ def call(self, service, method, *args, **kwargs): } http_headers = { - 'User-Agent': USER_AGENT, + 'User-Agent': consts.USER_AGENT, 'Content-Type': 'application/xml', } @@ -174,17 +174,17 @@ def call(self, service, method, *args, **kwargs): http_headers.update(kwargs.get('raw_headers')) uri = '/'.join([self.endpoint_url, service]) - return make_xml_rpc_api_call(uri, method, args, - headers=headers, - http_headers=http_headers, - timeout=self.timeout, - proxy=self.proxy) + return transports.make_xml_rpc_api_call(uri, method, args, + headers=headers, + http_headers=http_headers, + timeout=self.timeout, + proxy=self.proxy) __call__ = call def iter_call(self, service, method, chunk=100, limit=None, offset=0, *args, **kwargs): - """ A generator that deals with paginating through results. + """A generator that deals with paginating through results. :param service: the name of the SoftLayer API service :param method: the method to call on the service @@ -234,7 +234,7 @@ def iter_call(self, service, method, break def __format_object_mask(self, objectmask, service): - """ Format new and old style object masks into proper headers. + """Format new and old style object masks into proper headers. :param objectmask: a string- or dict-based object mask :param service: a SoftLayer API service name @@ -246,15 +246,15 @@ def __format_object_mask(self, objectmask, service): mheader = self._prefix + 'ObjectMask' objectmask = objectmask.strip() - if not objectmask.startswith('mask') \ - and not objectmask.startswith('['): + if (not objectmask.startswith('mask') + and not objectmask.startswith('[')): objectmask = "mask[%s]" % objectmask return {mheader: {'mask': objectmask}} def __repr__(self): - return "" \ - % (self.endpoint_url, self.auth) + return "" % (self.endpoint_url, + self.auth) __str__ = __repr__ @@ -263,11 +263,12 @@ def __len__(self): class TimedClient(Client): - """ Subclass of Client() + """Client that records API call timings. Using this class will time every call to the API and store it in an internal list. This will have a slight impact on your client's memory usage and performance. You should only use this for debugging. + """ def __init__(self, *args, **kwargs): @@ -275,7 +276,7 @@ def __init__(self, *args, **kwargs): super(TimedClient, self).__init__(*args, **kwargs) def call(self, service, method, *args, **kwargs): - """ See Client.call for documentation. """ + """See Client.call for documentation.""" start_time = time.time() result = super(TimedClient, self).call(service, method, *args, **kwargs) @@ -285,7 +286,7 @@ def call(self, service, method, *args, **kwargs): return result def get_last_calls(self): - """ Retrieves the last_calls property. + """Retrieves the last_calls property. This property will contain a list of tuples in the form ('SERVICE.METHOD', initiated_utc_timestamp, execution_time) @@ -296,7 +297,8 @@ def get_last_calls(self): class Service(object): - """ A SoftLayer Service. + """A SoftLayer Service. + :param client: A SoftLayer.API.Client instance :param name str: The service name @@ -306,7 +308,7 @@ def __init__(self, client, name): self.name = name def call(self, name, *args, **kwargs): - """ Make a SoftLayer API call + """Make a SoftLayer API call. :param method: the method to call on the service :param \\*args: (optional) arguments for the remote call @@ -333,7 +335,7 @@ def call(self, name, *args, **kwargs): __call__ = call def iter_call(self, name, *args, **kwargs): - """ A generator that deals with paginating through results. + """A generator that deals with paginating through results. :param method: the method to call on the service :param integer chunk: result size for each API call diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index c2e5f7fc4..10dc31fed 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -44,15 +44,16 @@ """ # :license: MIT, see LICENSE for more details. -import sys import logging +import sys -from docopt import docopt, DocoptExit +import docopt -from SoftLayer import Client, TimedClient, SoftLayerError, SoftLayerAPIError -from SoftLayer.consts import VERSION -from .helpers import CLIAbort, ArgumentError, format_output, KeyValueTable -from .environment import Environment, InvalidCommand, InvalidModule +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import consts DEBUG_LOGGING_MAP = { @@ -66,7 +67,7 @@ class CommandParser(object): - """ Helper class to parse commands + """Helper class to parse commands. :param env: Environment instance """ @@ -74,17 +75,17 @@ def __init__(self, env): self.env = env def get_main_help(self): - """ Get main help text """ + """Get main help text.""" return __doc__.strip() def get_module_help(self, module_name): - """ Get help text for a module """ + """Get help text for a module.""" module = self.env.load_module(module_name) arg_doc = module.__doc__ return arg_doc.strip() def get_command_help(self, module_name, command_name): - """ Get help text for a specific command """ + """Get help text for a specific command.""" command = self.env.get_command(module_name, command_name) default_format = 'raw' @@ -113,35 +114,37 @@ def get_command_help(self, module_name, command_name): return arg_doc.strip() def parse_main_args(self, args): - """ Parse root arguments """ + """Parse root arguments.""" main_help = self.get_main_help() - arguments = docopt( + arguments = docopt.docopt( main_help, - version=VERSION, + version=consts.VERSION, argv=args, options_first=True) arguments[''] = self.env.get_module_name(arguments['']) return arguments def parse_module_args(self, module_name, args): - """ Parse module arguments """ + """Parse module arguments.""" arg_doc = self.get_module_help(module_name) - arguments = docopt( + arguments = docopt.docopt( arg_doc, - version=VERSION, + version=consts.VERSION, argv=[module_name] + args, options_first=True) return arguments def parse_command_args(self, module_name, command_name, args): - """ Parse command arguments """ + """Parse command arguments.""" command = self.env.get_command(module_name, command_name) arg_doc = self.get_command_help(module_name, command_name) - arguments = docopt(arg_doc, version=VERSION, argv=[module_name] + args) + arguments = docopt.docopt(arg_doc, + version=consts.VERSION, + argv=[module_name] + args) return command, arguments def parse(self, args): - """ Parse entire tree of arguments """ + """Parse entire tree of arguments.""" # handle `sl ...` main_args = self.parse_main_args(args) module_name = main_args[''] @@ -159,10 +162,8 @@ def parse(self, args): main_args['']) -def main(args=sys.argv[1:], env=Environment()): - """ - Entry point for the command-line client. - """ +def main(args=sys.argv[1:], env=environment.Environment()): + """Entry point for the command-line client.""" # Parse Top-Level Arguments exit_status = 0 resolver = CommandParser(env) @@ -182,9 +183,9 @@ def main(args=sys.argv[1:], env=Environment()): 'config_file': command_args.get('--config') } if command_args.get('--timings'): - client = TimedClient(**kwargs) + client = SoftLayer.TimedClient(**kwargs) else: - client = Client(**kwargs) + client = SoftLayer.Client(**kwargs) # Do the thing runnable = command(client=client, env=env) @@ -192,34 +193,35 @@ def main(args=sys.argv[1:], env=Environment()): if data: out_format = command_args.get('--format', 'table') if out_format not in VALID_FORMATS: - raise ArgumentError('Invalid format "%s"' % out_format) - output = format_output(data, fmt=out_format) + raise exceptions.ArgumentError('Invalid format "%s"' + % out_format) + output = formatting.format_output(data, fmt=out_format) if output: env.out(output) if command_args.get('--timings'): out_format = command_args.get('--format', 'table') api_calls = client.get_last_calls() - timing_table = KeyValueTable(['call', 'time']) + timing_table = formatting.KeyValueTable(['call', 'time']) for call, _, duration in api_calls: timing_table.add_row([call, duration]) - env.err(format_output(timing_table, fmt=out_format)) + env.err(formatting.format_output(timing_table, fmt=out_format)) - except InvalidCommand as ex: + except exceptions.InvalidCommand as ex: env.err(resolver.get_module_help(ex.module_name)) if ex.command_name: env.err('') env.err(str(ex)) exit_status = 1 - except InvalidModule as ex: + except exceptions.InvalidModule as ex: env.err(resolver.get_main_help()) if ex.module_name: env.err('') env.err(str(ex)) exit_status = 1 - except DocoptExit as ex: + except docopt.DocoptExit as ex: env.err(ex.usage) env.err( '\nUnknown argument(s), use -h or --help for available options') @@ -227,19 +229,19 @@ def main(args=sys.argv[1:], env=Environment()): except KeyboardInterrupt: env.out('') exit_status = 1 - except CLIAbort as ex: + except exceptions.CLIAbort as ex: env.err(str(ex.message)) exit_status = ex.code except SystemExit as ex: exit_status = ex.code - except SoftLayerAPIError as ex: + except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' in ex.faultString.lower(): env.out("Authentication Failed: To update your credentials, use " "'sl config setup'") else: env.err(str(ex)) exit_status = 1 - except SoftLayerError as ex: + except SoftLayer.SoftLayerError as ex: env.err(str(ex)) exit_status = 1 except Exception: diff --git a/SoftLayer/CLI/environment.py b/SoftLayer/CLI/environment.py index c6e27f06a..58a024c4c 100644 --- a/SoftLayer/CLI/environment.py +++ b/SoftLayer/CLI/environment.py @@ -6,38 +6,21 @@ :license: MIT, see LICENSE for more details. """ import getpass -from importlib import import_module +import importlib import inspect import os import os.path import sys -from SoftLayer.CLI.modules import get_module_list -from SoftLayer.utils import console_input -from SoftLayer import SoftLayerError +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import modules +from SoftLayer import utils # pylint: disable=R0201 -class InvalidCommand(SoftLayerError): - " Raised when trying to use a command that does not exist " - def __init__(self, module_name, command_name, *args): - self.module_name = module_name - self.command_name = command_name - error = 'Invalid command: "%s".' % self.command_name - SoftLayerError.__init__(self, error, *args) - - -class InvalidModule(SoftLayerError): - " Raised when trying to use a module that does not exist " - def __init__(self, module_name, *args): - self.module_name = module_name - error = 'Invalid module: "%s".' % self.module_name - SoftLayerError.__init__(self, error, *args) - - class Environment(object): - """ Provides access to the current CLI environment """ + """Provides access to the current CLI environment.""" def __init__(self): # {'module_name': {'action': 'actionClass'}} self.plugins = {} @@ -56,70 +39,74 @@ def __init__(self): self.stderr = sys.stderr def get_command(self, module_name, command_name): - """ Based on the loaded modules, return a command """ + """Based on the loaded modules, return a command.""" actions = self.plugins.get(module_name) or {} if command_name in actions: return actions[command_name] if None in actions: return actions[None] - raise InvalidCommand(module_name, command_name) + raise exceptions.InvalidCommand(module_name, command_name) def get_module_name(self, module_name): - """ Returns the actual module name. Uses the alias mapping """ + """Returns the actual module name. Uses the alias mapping.""" if module_name in self.aliases: return self.aliases[module_name] return module_name def load_module(self, module_name): # pragma: no cover - """ Loads module by name """ + """Loads module by name.""" try: - module = import_module('SoftLayer.CLI.modules.%s' % module_name) + module = importlib.import_module('SoftLayer.CLI.modules.%s' + % module_name) for _, obj in inspect.getmembers(module): if inspect.isclass(obj) and issubclass(obj, CLIRunnable): self.add_plugin(obj) return module except ImportError: - raise InvalidModule(module_name) + raise exceptions.InvalidModule(module_name) def add_plugin(self, cls): - """ Add a CLIRunnable as a plugin to the environment """ + """Add a CLIRunnable as a plugin to the environment.""" command = cls.__module__.split('.')[-1] if command not in self.plugins: self.plugins[command] = {} self.plugins[command][cls.action] = cls def plugin_list(self): - """ Returns the list of modules in SoftLayer.CLI.modules """ - return get_module_list() + """Returns the list of modules in SoftLayer.CLI.modules.""" + return modules.get_module_list() def out(self, output, newline=True): - """ Outputs a string to the console (stdout) """ + """Outputs a string to the console (stdout).""" self.stdout.write(output) if newline: self.stdout.write(os.linesep) def err(self, output, newline=True): - """ Outputs an error string to the console (stderr) """ + """Outputs an error string to the console (stderr).""" self.stderr.write(output) if newline: self.stderr.write(os.linesep) def input(self, prompt): - """ Provide a command prompt """ - return console_input(prompt) + """Provide a command prompt.""" + return utils.console_input(prompt) def getpass(self, prompt): - """ Provide a password prompt """ + """Provide a password prompt.""" return getpass.getpass(prompt) def exit(self, code=0): - """ Exit """ + """Exit.""" sys.exit(code) class CLIRunnable(object): - """ CLIRunnable is intended to be subclassed. It represents a descrete - command or action in the CLI. """ + """This represents a descrete command or action in the CLI. + + CLIRunnable is intended to be subclassed. + + """ options = [] # set by subclass action = 'not set' # set by subclass @@ -128,6 +115,9 @@ def __init__(self, client=None, env=None): self.env = env def execute(self, args): - """ Execute the command. This is intended to be overridden in a - subclass """ + """Execute the command. + + This is intended to be overridden in a subclass. + + """ pass diff --git a/SoftLayer/CLI/exceptions.py b/SoftLayer/CLI/exceptions.py index 100966945..b1c725ef4 100644 --- a/SoftLayer/CLI/exceptions.py +++ b/SoftLayer/CLI/exceptions.py @@ -6,23 +6,42 @@ :license: MIT, see LICENSE for more details. """ +import SoftLayer + class CLIHalt(SystemExit): - """ Smoothly halt the execution of the command. No error """ + """Smoothly halt the execution of the command. No error.""" def __init__(self, code=0, *args): super(CLIHalt, self).__init__(*args) self.code = code class CLIAbort(CLIHalt): - """ Halt the execution of the command. Gives an exit code of 2 """ + """Halt the execution of the command. Gives an exit code of 2.""" def __init__(self, msg, *args): super(CLIAbort, self).__init__(code=2, *args) self.message = msg class ArgumentError(CLIAbort): - """ Halt the execution of the command because of invalid arguments. """ + """Halt the execution of the command because of invalid arguments.""" def __init__(self, msg, *args): super(ArgumentError, self).__init__(msg, *args) self.message = "Argument Error: %s" % msg + + +class InvalidCommand(SoftLayer.SoftLayerError): + """Raised when trying to use a command that does not exist.""" + def __init__(self, module_name, command_name, *args): + self.module_name = module_name + self.command_name = command_name + error = 'Invalid command: "%s".' % self.command_name + SoftLayer.SoftLayerError.__init__(self, error, *args) + + +class InvalidModule(SoftLayer.SoftLayerError): + """Raised when trying to use a module that does not exist.""" + def __init__(self, module_name, *args): + self.module_name = module_name + error = 'Invalid module: "%s".' % self.module_name + SoftLayer.SoftLayerError.__init__(self, error, *args) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index b3ba88661..c8facc606 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -7,22 +7,24 @@ :license: MIT, see LICENSE for more details. """ # pylint: disable=E0202 -import os import json +import os + +import prettytable -from prettytable import PrettyTable, FRAME, NONE +from SoftLayer import utils -from SoftLayer.utils import string_types, console_input +FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False'] def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 - """ Given some data, will format it for output + """Given some data, will format it for console output. :param data: One of: String, Table, FormattedItem, List, Tuple, SequentialOutput :param string fmt (optional): One of: table, raw, json, python """ - if isinstance(data, string_types): + if isinstance(data, utils.string_types): if fmt == 'json': return json.dumps(data) return data @@ -66,13 +68,12 @@ def format_output(data, fmt='table'): # pylint: disable=R0911,R0912 def format_prettytable(table): - """ Takes a SoftLayer.CLI.formatting.Table instance and returns a formatted - prettytable """ + """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item) ptable = table.prettytable() - ptable.hrules = FRAME + ptable.hrules = prettytable.FRAME ptable.horizontal_char = '.' ptable.vertical_char = ':' ptable.junction_char = ':' @@ -80,15 +81,14 @@ def format_prettytable(table): def format_no_tty(table): - """ Takes a SoftLayer.CLI.formatting.Table instance and returns a formatted - prettytable that has as little formatting as possible """ + """Converts SoftLayer.CLI.formatting.Table instance to a prettytable.""" for i, row in enumerate(table.rows): for j, item in enumerate(row): table.rows[i][j] = format_output(item, fmt='raw') ptable = table.prettytable() for col in table.columns: ptable.align[col] = 'l' - ptable.hrules = NONE + ptable.hrules = prettytable.NONE ptable.border = False ptable.header = False ptable.left_padding_width = 0 @@ -97,8 +97,7 @@ def format_no_tty(table): def mb_to_gb(megabytes): - """ Takes in the number of megabytes and returns a FormattedItem that - displays gigabytes. + """Converts number of megabytes to a FormattedItem in gigabytes. :param int megabytes: number of megabytes """ @@ -106,8 +105,7 @@ def mb_to_gb(megabytes): def gb(gigabytes): # pylint: disable=C0103 - """ Takes in the number of gigabytes and returns a FormattedItem that - displays gigabytes. + """Converts number of gigabytes to a FormattedItem in gigabytes. :param int gigabytes: number of gigabytes """ @@ -116,15 +114,12 @@ def gb(gigabytes): # pylint: disable=C0103 def blank(): - """ Returns FormattedItem to make pretty output use a dash - and raw formatting to use NULL - """ + """Returns a blank FormattedItem.""" return FormattedItem(None, '-') def listing(items, separator=','): - """ Given an iterable, returns a FormattedItem which display a list of - items + """Given an iterable return a FormattedItem which display the list of items :param items: An iterable that outputs strings :param string separator: the separator to use @@ -133,23 +128,17 @@ def listing(items, separator=','): def active_txn(item): - """ Returns a FormattedItem describing the active transaction (if any) on - the given object. If no active transaction is running, returns a blank - FormattedItem. + """Returns a FormattedItem describing the active transaction on a object. + + If no active transaction is running, returns a blank FormattedItem. :param item: An object capable of having an active transaction """ - if not item['activeTransaction'].get('transactionStatus'): - return blank() - - return FormattedItem( - item['activeTransaction']['transactionStatus'].get('name'), - item['activeTransaction']['transactionStatus'].get('friendlyName')) + return transaction_status(item['activeTransaction']) def transaction_status(transaction): - """ Returns a FormattedItem describing the transaction status (if any) on - the given object. If there is no status, returns a blank FormattedItem. + """Returns a FormattedItem describing the given transaction. :param item: An object capable of having an active transaction """ @@ -162,15 +151,17 @@ def transaction_status(transaction): def valid_response(prompt, *valid): - """ Will display a prompt for a command-line user. If the input is in the - valid given valid list then it will return True. Otherwise, it will - return False. If no input is received from the user, None is returned - instead. + """Prompt user for input. + + Will display a prompt for a command-line user. If the input is in the + valid given valid list then it will return True. Otherwise, it will + return False. If no input is received from the user, None is returned + instead. :param string prompt: string prompt to give to the user :param string \\*valid: valid responses """ - ans = console_input(prompt).lower() + ans = utils.console_input(prompt).lower() if ans in valid: return True @@ -181,7 +172,7 @@ def valid_response(prompt, *valid): def confirm(prompt_str, default=False): - """ Show a confirmation prompt to a command-line user. + """Show a confirmation prompt to a command-line user. :param string prompt_str: prompt to give to the user :param bool default: Default value to True or False @@ -200,7 +191,7 @@ def confirm(prompt_str, default=False): def no_going_back(confirmation): - """ Show a confirmation to a user. + """Show a confirmation to a user. :param confirmation str: the string the user has to enter in order to confirm their action. @@ -215,8 +206,9 @@ def no_going_back(confirmation): class SequentialOutput(list): - """ This object represents output in a sequence. The purpose is to - de-couple the separator from the output itself. + """SequentialOutput is used for outputting sequential items. + + The purpose is to de-couple the separator from the output itself. :param separator str: string to use as a default separator """ @@ -225,7 +217,7 @@ def __init__(self, separator=os.linesep, *args, **kwargs): super(SequentialOutput, self).__init__(*args, **kwargs) def to_python(self): - """ returns itself, since it itself is a list """ + """returns itself, since it itself is a list.""" return self def __str__(self): @@ -233,18 +225,16 @@ def __str__(self): class CLIJSONEncoder(json.JSONEncoder): - " A JSON encoder which is able to use a .to_python() method on objects. " + """A JSON encoder which is able to use a .to_python() method on objects.""" def default(self, obj): - """ If the normal JSONEncoder doesn't understand, we have a chance to - encode the object into a native python type. - """ + """Encode object if it implements to_python().""" if hasattr(obj, 'to_python'): return obj.to_python() return super(CLIJSONEncoder, self).default(obj) class Table(object): - """ A Table structure. + """A Table structure used for output. :param list columns: a list of column names """ @@ -256,14 +246,14 @@ def __init__(self, columns): self.sortby = None def add_row(self, row): - """ Add a row to the table + """Add a row to the table. :param list row: the row of string to be added """ self.rows.append(row) def to_python(self): - """ Decode this Table object to standard Python types """ + """Decode this Table object to standard Python types.""" # Adding rows items = [] for row in self.rows: @@ -272,8 +262,8 @@ def to_python(self): return items def prettytable(self): - """ Returns a new prettytable instance. """ - table = PrettyTable(self.columns) + """Returns a new prettytable instance.""" + table = prettytable.PrettyTable(self.columns) if self.sortby: table.sortby = self.sortby for a_col, alignment in self.align.items(): @@ -286,10 +276,9 @@ def prettytable(self): class KeyValueTable(Table): - """ This is a Table which is intended to be used to display key-value - pairs. It expects there to be only two columns.""" + """A table that is oriented towards key-value pairs.""" def to_python(self): - """ Decode this KeyValueTable object to standard Python types """ + """Decode this KeyValueTable object to standard Python types.""" mapping = {} for row in self.rows: mapping[row[0]] = _format_python_value(row[1]) @@ -297,8 +286,7 @@ def to_python(self): class FormattedItem(object): - """ This is an object which wraps a single value that is able to be - represented in a human readable and a machine-readable way. + """This is an object that can be displayed as a human readable and raw. :param original: raw (machine-readable) value :param string formatted: human-readable value @@ -311,11 +299,11 @@ def __init__(self, original, formatted=None): self.formatted = self.original def to_python(self): - """ returns the original (raw) value """ + """returns the original (raw) value.""" return self.original def __str__(self): - """ returns the formatted value """ + """returns the formatted value.""" # If the original value is None, represent this as 'NULL' if self.original is None: return 'NULL' @@ -325,7 +313,7 @@ def __str__(self): def _format_python_value(value): - """ If the value has to_python() defined then return that """ + """If the value has to_python() defined then return that.""" if hasattr(value, 'to_python'): return value.to_python() return value diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index 6c9e928ad..135e36052 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -6,34 +6,11 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.utils import NestedDict -from SoftLayer.CLI.environment import CLIRunnable -from .exceptions import CLIHalt, CLIAbort, ArgumentError -from .formatting import ( - Table, KeyValueTable, FormattedItem, SequentialOutput, confirm, - no_going_back, mb_to_gb, gb, listing, blank, format_output, - active_txn, valid_response, transaction_status) -from .template import update_with_template_args, export_to_template - -__all__ = [ - # Core/Misc - 'CLIRunnable', 'NestedDict', 'FALSE_VALUES', 'resolve_id', - # Exceptions - 'CLIAbort', 'CLIHalt', 'ArgumentError', - # Formatting - 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', - 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', - 'listing', 'format_output', 'blank', 'active_txn', 'transaction_status', - # Template - 'update_with_template_args', 'export_to_template', -] - -FALSE_VALUES = ['0', 'false', 'FALSE', 'no', 'False'] +from SoftLayer.CLI import exceptions def resolve_id(resolver, identifier, name='object'): - """ Resolves a single id using an id resolver function which returns a list - of ids. + """Resolves a single id using a resolver function. :param resolver: function that resolves ids. Should return None or a list of ids. @@ -44,10 +21,11 @@ def resolve_id(resolver, identifier, name='object'): ids = resolver(identifier) if len(ids) == 0: - raise CLIAbort("Error: Unable to find %s '%s'" % (name, identifier)) + raise exceptions.CLIAbort("Error: Unable to find %s '%s'" + % (name, identifier)) if len(ids) > 1: - raise CLIAbort( + raise exceptions.CLIAbort( "Error: Multiple %s found for '%s': %s" % (name, identifier, ', '.join([str(_id) for _id in ids]))) diff --git a/SoftLayer/CLI/modules/__init__.py b/SoftLayer/CLI/modules/__init__.py index 99add6466..eaad02f96 100644 --- a/SoftLayer/CLI/modules/__init__.py +++ b/SoftLayer/CLI/modules/__init__.py @@ -6,10 +6,10 @@ :license: MIT, see LICENSE for more details. """ -from pkgutil import iter_modules +import pkgutil def get_module_list(): - """ Returns each module under SoftLayer.CLI.modules """ - actions = [action[1] for action in iter_modules(__path__)] + """Returns each module under SoftLayer.CLI.modules.""" + actions = [action[1] for action in pkgutil.iter_modules(__path__)] return actions diff --git a/SoftLayer/CLI/modules/cdn.py b/SoftLayer/CLI/modules/cdn.py index 67c4a21d6..11f0e8b47 100644 --- a/SoftLayer/CLI/modules/cdn.py +++ b/SoftLayer/CLI/modules/cdn.py @@ -14,11 +14,12 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import CLIRunnable, Table, KeyValueTable, blank -from SoftLayer.managers.cdn import CDNManager +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting -class ListAccounts(CLIRunnable): +class ListAccounts(environment.CLIRunnable): """ usage: sl cdn list [options] @@ -31,24 +32,28 @@ class ListAccounts(CLIRunnable): action = 'list' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) accounts = manager.list_accounts() - table = Table(['id', 'account_name', 'type', 'created', 'notes']) + table = formatting.Table(['id', + 'account_name', + 'type', + 'created', + 'notes']) for account in accounts: table.add_row([ account['id'], account['cdnAccountName'], account['cdnSolutionName'], account['createDate'], - account.get('cdnAccountNote', blank()) + account.get('cdnAccountNote', formatting.blank()) ]) table.sortby = args['--sortby'] return table -class DetailAccount(CLIRunnable): +class DetailAccount(environment.CLIRunnable): """ usage: sl cdn detail [options] @@ -57,10 +62,10 @@ class DetailAccount(CLIRunnable): action = 'detail' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) account = manager.get_account(args.get('')) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' @@ -69,12 +74,13 @@ def execute(self, args): 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', blank())]) + table.add_row(['notes', + account.get('cdnAccountNote', formatting.blank())]) return table -class LoadContent(CLIRunnable): +class LoadContent(environment.CLIRunnable): """ usage: sl cdn load ... [options] @@ -89,11 +95,11 @@ class LoadContent(CLIRunnable): required_params = ['account', 'content_url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.load_content(args.get(''), args.get('')) -class PurgeContent(CLIRunnable): +class PurgeContent(environment.CLIRunnable): """ usage: sl cdn purge ... [options] @@ -108,12 +114,12 @@ class PurgeContent(CLIRunnable): required_params = ['account', 'content_url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.purge_content(args.get(''), args.get('')) -class ListOrigins(CLIRunnable): +class ListOrigins(environment.CLIRunnable): """ usage: sl cdn origin-list [options] @@ -122,21 +128,21 @@ class ListOrigins(CLIRunnable): action = 'origin-list' def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) origins = manager.get_origins(args.get('')) - table = Table(['id', 'media_type', 'cname', 'origin_url']) + table = formatting.Table(['id', 'media_type', 'cname', 'origin_url']) for origin in origins: table.add_row([origin['id'], origin['mediaType'], - origin.get('cname', blank()), + origin.get('cname', formatting.blank()), origin['originUrl']]) return table -class AddOrigin(CLIRunnable): +class AddOrigin(environment.CLIRunnable): """ usage: sl cdn origin-add [options] @@ -156,14 +162,14 @@ class AddOrigin(CLIRunnable): required_params = ['account', 'url'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) media_type = args.get('--type') or 'http' manager.add_origin(args.get(''), media_type, args.get(''), args.get('--cname', None)) -class RemoveOrigin(CLIRunnable): +class RemoveOrigin(environment.CLIRunnable): """ usage: sl cdn origin-remove [options] @@ -177,6 +183,6 @@ class RemoveOrigin(CLIRunnable): required_params = ['account', 'origin_id'] def execute(self, args): - manager = CDNManager(self.client) + manager = SoftLayer.CDNManager(self.client) manager.remove_origin(args.get(''), args.get('')) diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index 5a8fe62b8..3f84e1052 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -11,12 +11,12 @@ import os.path -from SoftLayer import ( - SoftLayerAPIError, API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT) -from SoftLayer.auth import BasicAuthentication -from SoftLayer.CLI import ( - CLIRunnable, CLIAbort, KeyValueTable, confirm, format_output) -from SoftLayer.utils import configparser +import SoftLayer +from SoftLayer import auth +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer import utils def get_settings_from_client(client): @@ -41,7 +41,7 @@ def get_settings_from_client(client): def config_table(settings): """ Returns a config table """ - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['Username', settings['username'] or 'not set']) @@ -52,18 +52,20 @@ def config_table(settings): def get_api_key(client, username, secret, endpoint_url=None): - """ Tries API-Key and password auth to get (and potentially generate) an - API key. """ + """ Attempts API-Key and password auth to get an API key + + This will also generate an API key if one doesn't exist + """ client.endpoint_url = endpoint_url client.auth = None # Try to use a client with username/api key if len(secret) == 64: try: - client.auth = BasicAuthentication(username, secret) + client.auth = auth.BasicAuthentication(username, secret) client['Account'].getCurrentUser() return secret - except SoftLayerAPIError as ex: + except SoftLayer.SoftLayerAPIError as ex: if 'invalid api token' not in ex.faultString.lower(): raise else: @@ -79,7 +81,7 @@ def get_api_key(client, username, secret, endpoint_url=None): return api_keys[0]['authenticationKey'] -class Setup(CLIRunnable): +class Setup(environment.CLIRunnable): """ usage: sl config setup [options] @@ -99,22 +101,23 @@ def execute(self, args): config_path = os.path.expanduser(path) self.env.out( - format_output(config_table({'username': username, - 'api_key': api_key, - 'endpoint_url': endpoint_url, - 'timeout': timeout}))) + formatting.format_output(config_table({ + 'username': username, + 'api_key': api_key, + 'endpoint_url': endpoint_url, + 'timeout': timeout}))) - if not confirm('Are you sure you want to write settings to "%s"?' - % config_path, default=True): - raise CLIAbort('Aborted.') + 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 # setting the values to avoid clobbering settings - config = configparser.RawConfigParser() + config = utils.configparser.RawConfigParser() config.read(config_path) try: config.add_section('softlayer') - except configparser.DuplicateSectionError: + except utils.configparser.DuplicateSectionError: pass config.set('softlayer', 'username', username) @@ -140,23 +143,23 @@ def get_user_input(self): # Ask for username for _ in range(3): - username = self.env.input( - 'Username [%s]: ' % defaults['username']) \ - or defaults['username'] + username = (self.env.input('Username [%s]: ' + % defaults['username']) + or defaults['username']) if username: break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') # Ask for 'secret' which can be api_key or their password for _ in range(3): - secret = self.env.getpass( - 'API Key or Password [%s]: ' % defaults['api_key']) \ - or defaults['api_key'] + secret = (self.env.getpass('API Key or Password [%s]: ' + % defaults['api_key']) + or defaults['api_key']) if secret: break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') # Ask for which endpoint they want to use for _ in range(3): @@ -164,13 +167,13 @@ def get_user_input(self): 'Endpoint (public|private|custom): ') endpoint_type = endpoint_type.lower() if not endpoint_type: - endpoint_url = API_PUBLIC_ENDPOINT + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT break if endpoint_type == 'public': - endpoint_url = API_PUBLIC_ENDPOINT + endpoint_url = SoftLayer.API_PUBLIC_ENDPOINT break elif endpoint_type == 'private': - endpoint_url = API_PRIVATE_ENDPOINT + endpoint_url = SoftLayer.API_PRIVATE_ENDPOINT break elif endpoint_type == 'custom': endpoint_url = self.env.input( @@ -178,12 +181,12 @@ def get_user_input(self): ) or defaults['endpoint_url'] break else: - raise CLIAbort('Aborted after 3 attempts') + raise exceptions.CLIAbort('Aborted after 3 attempts') return username, secret, endpoint_url, timeout -class Show(CLIRunnable): +class Show(environment.CLIRunnable): """ usage: sl config show [options] diff --git a/SoftLayer/CLI/modules/dns.py b/SoftLayer/CLI/modules/dns.py index 8b7fe6402..0d9d6f389 100755 --- a/SoftLayer/CLI/modules/dns.py +++ b/SoftLayer/CLI/modules/dns.py @@ -16,12 +16,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import ( - CLIRunnable, no_going_back, Table, CLIAbort, resolve_id) -from SoftLayer import DNSManager +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class DumpZone(CLIRunnable): +class DumpZone(environment.CLIRunnable): """ usage: sl dns print [options] @@ -33,12 +35,13 @@ class DumpZone(CLIRunnable): action = "print" def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') return manager.dump_zone(zone_id) -class CreateZone(CLIRunnable): +class CreateZone(environment.CLIRunnable): """ usage: sl dns create [options] @@ -50,11 +53,11 @@ class CreateZone(CLIRunnable): action = 'create' def execute(self, args): - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) manager.create_zone(args['']) -class DeleteZone(CLIRunnable): +class DeleteZone(environment.CLIRunnable): """ usage: sl dns delete [options] @@ -67,16 +70,17 @@ class DeleteZone(CLIRunnable): options = ['confirm'] def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') - if args['--really'] or no_going_back(args['']): + if args['--really'] or formatting.no_going_back(args['']): manager.delete_zone(zone_id) else: - raise CLIAbort("Aborted.") + raise exceptions.CLIAbort("Aborted.") -class ListZones(CLIRunnable): +class ListZones(environment.CLIRunnable): """ usage: sl dns list [] [options] @@ -98,14 +102,15 @@ def execute(self, args): def list_zone(self, args): """ list records for a particular zone """ - manager = DNSManager(self.client) - table = Table(['id', 'record', 'type', 'ttl', 'value']) + manager = SoftLayer.DNSManager(self.client) + table = formatting.Table(['id', 'record', 'type', 'ttl', 'value']) table.align['ttl'] = 'l' table.align['record'] = 'r' table.align['value'] = 'l' - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') records = manager.get_records( zone_id, @@ -128,9 +133,9 @@ def list_zone(self, args): def list_all_zones(self): """ List all zones """ - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) zones = manager.list_zones() - table = Table(['id', 'zone', 'serial', 'updated']) + table = formatting.Table(['id', 'zone', 'serial', 'updated']) table.align['serial'] = 'c' table.align['updated'] = 'c' @@ -145,7 +150,7 @@ def list_all_zones(self): return table -class AddRecord(CLIRunnable): +class AddRecord(environment.CLIRunnable): """ usage: sl dns add [--ttl=TTL] [options] @@ -164,9 +169,10 @@ class AddRecord(CLIRunnable): action = 'add' def execute(self, args): - manager = DNSManager(self.client) + manager = SoftLayer.DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') manager.create_record( zone_id, @@ -176,7 +182,7 @@ def execute(self, args): ttl=args['--ttl'] or 7200) -class EditRecord(CLIRunnable): +class EditRecord(environment.CLIRunnable): """ usage: sl dns edit [--data=DATA] [--ttl=TTL] [--id=ID] [options] @@ -195,8 +201,9 @@ class EditRecord(CLIRunnable): action = 'edit' def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') results = manager.get_records( zone_id, @@ -210,7 +217,7 @@ def execute(self, args): manager.edit_record(result) -class RecordRemove(CLIRunnable): +class RecordRemove(environment.CLIRunnable): """ usage: sl dns remove [--id=ID] [options] @@ -227,8 +234,9 @@ class RecordRemove(CLIRunnable): options = ['confirm'] def execute(self, args): - manager = DNSManager(self.client) - zone_id = resolve_id(manager.resolve_ids, args[''], name='zone') + manager = SoftLayer.DNSManager(self.client) + zone_id = helpers.resolve_id(manager.resolve_ids, args[''], + name='zone') if args['--id']: records = [{'id': args['--id']}] @@ -237,11 +245,11 @@ def execute(self, args): zone_id, host=args['']) - if args['--really'] or no_going_back('yes'): - table = Table(['record']) + if args['--really'] or formatting.no_going_back('yes'): + table = formatting.Table(['record']) for result in records: manager.delete_record(result['id']) table.add_row([result['id']]) return table - raise CLIAbort("Aborted.") + raise exceptions.CLIAbort("Aborted.") diff --git a/SoftLayer/CLI/modules/firewall.py b/SoftLayer/CLI/modules/firewall.py index b4e4b93f3..489cf829a 100755 --- a/SoftLayer/CLI/modules/firewall.py +++ b/SoftLayer/CLI/modules/firewall.py @@ -14,14 +14,16 @@ # :license: MIT, see LICENSE for more details. from __future__ import print_function -from subprocess import call import os +import subprocess import tempfile -from SoftLayer import FirewallManager, SoftLayerError -from SoftLayer.utils import lookup -from SoftLayer.CLI import CLIRunnable, Table, listing, resolve_id, confirm -from SoftLayer.CLI.helpers import blank, CLIAbort +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 DELIMITER = "=========================================\n" @@ -34,8 +36,8 @@ def get_ids(input_id): key_value = input_id.split(':') if len(key_value) != 2: - raise CLIAbort('Invalid ID %s: ID should be of the form xxx:yyy' - % input_id) + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) return key_value @@ -69,8 +71,8 @@ def get_rules_table(rules): :param list rules: A list containing the rules of the firewall :returns: a formatted table of the firewall rules """ - table = Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', 'dest', - 'dest_mask']) + table = formatting.Table(['#', 'action', 'protocol', 'src_ip', 'src_mask', + 'dest', 'dest_mask']) table.sortby = '#' for rule in rules: table.add_row([ @@ -137,7 +139,7 @@ def open_editor(rules=None, content=None): # if content is provided, just display it as is tfile.write(content) tfile.flush() - call([editor, tfile.name]) + subprocess.call([editor, tfile.name]) tfile.seek(0) data = tfile.read() return data @@ -153,7 +155,7 @@ def open_editor(rules=None, content=None): tfile.write(get_formatted_rule(rule)) tfile.write(DELIMITER) tfile.flush() - call([editor, tfile.name]) + subprocess.call([editor, tfile.name]) tfile.seek(0) data = tfile.read() return data @@ -205,7 +207,7 @@ def parse_rules(content=None): return parsed_rules -class FWList(CLIRunnable): +class FWList(environment.CLIRunnable): """ usage: sl firewall list [options] @@ -214,11 +216,11 @@ class FWList(CLIRunnable): action = 'list' def execute(self, args): - mgr = FirewallManager(self.client) - table = Table(['firewall id', - 'type', - 'features', - 'server/vlan id']) + mgr = SoftLayer.FirewallManager(self.client) + table = formatting.Table(['firewall id', + 'type', + 'features', + 'server/vlan id']) fwvlans = mgr.get_firewalls() dedicated_firewalls = [firewall for firewall in fwvlans if firewall['dedicatedFirewallFlag']] @@ -229,9 +231,9 @@ def execute(self, args): features.append('HA') if features: - feature_list = listing(features, separator=',') + feature_list = formatting.listing(features, separator=',') else: - feature_list = blank() + feature_list = formatting.blank() table.add_row([ 'vlan:%s' % vlan['networkVlanFirewall']['id'], @@ -264,16 +266,16 @@ def execute(self, args): 'server:%s' % firewall['id'], 'Server - standard', '-', - lookup(firewall, - 'networkComponent', - 'downlinkComponent', - 'hardwareId') + utils.lookup(firewall, + 'networkComponent', + 'downlinkComponent', + 'hardwareId') ]) return table -class FWCancel(CLIRunnable): +class FWCancel(environment.CLIRunnable): """ usage: sl firewall cancel [options] @@ -287,23 +289,24 @@ class FWCancel(CLIRunnable): options = ['really'] def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) firewall_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a firewall" - " from your account. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "firewall from your account." + " Continue?"): if key_value[0] in ['cci', 'server']: mgr.cancel_firewall(firewall_id, dedicated=False) elif key_value[0] == 'vlan': mgr.cancel_firewall(firewall_id, dedicated=True) return 'Firewall with id %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class FWAdd(CLIRunnable): +class FWAdd(environment.CLIRunnable): """ usage: sl firewall add (--cci | --vlan | --server) [options] @@ -319,8 +322,8 @@ class FWAdd(CLIRunnable): options = ['really', 'ha'] def execute(self, args): - mgr = FirewallManager(self.client) - input_id = resolve_id( + mgr = SoftLayer.FirewallManager(self.client) + input_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'firewall') ha_support = args.get('--ha', False) if not args['--really']: @@ -335,9 +338,9 @@ def execute(self, args): return "Unable to add firewall - Is network public enabled?" print_package_info(pkg) - if not confirm("This action will incur charges on your account. " - "Continue?"): - raise CLIAbort('Aborted.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') if args['--vlan']: mgr.add_vlan_firewall(input_id, ha_enabled=ha_support) @@ -349,7 +352,7 @@ def execute(self, args): return "Firewall is being created!" -class FWDetails(CLIRunnable): +class FWDetails(environment.CLIRunnable): """ usage: sl firewall detail [options] @@ -358,7 +361,7 @@ class FWDetails(CLIRunnable): action = 'detail' def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -370,7 +373,7 @@ def execute(self, args): return get_rules_table(rules) -class FWEdit(CLIRunnable): +class FWEdit(environment.CLIRunnable): """ usage: sl firewall edit [options] @@ -379,7 +382,7 @@ class FWEdit(CLIRunnable): action = 'edit' def execute(self, args): - mgr = FirewallManager(self.client) + mgr = SoftLayer.FirewallManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -391,8 +394,8 @@ def execute(self, args): # open an editor for the user to enter their rules edited_rules = open_editor(rules=orig_rules) print(edited_rules) - if confirm("Would you like to submit the rules. " - "Continue?"): + if formatting.confirm("Would you like to submit the rules. " + "Continue?"): while True: try: rules = parse_rules(edited_rules) @@ -403,19 +406,19 @@ def execute(self, args): rules = mgr.edit_standard_fwl_rules(firewall_id, rules) break - except (SoftLayerError, ValueError) as error: + except (SoftLayer.SoftLayerError, ValueError) as error: print("Unexpected error({%s})" % (error)) - if confirm("Would you like to continue editing the rules" - ". Continue?"): + if formatting.confirm("Would you like to continue editing " + "the rules. Continue?"): edited_rules = open_editor(content=edited_rules) print(edited_rules) - if confirm("Would you like to submit the rules. " - "Continue?"): + if formatting.confirm("Would you like to submit the " + "rules. Continue?"): continue else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') return 'Firewall updated!' else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') diff --git a/SoftLayer/CLI/modules/globalip.py b/SoftLayer/CLI/modules/globalip.py index 5640d2dd2..6f87c2a10 100644 --- a/SoftLayer/CLI/modules/globalip.py +++ b/SoftLayer/CLI/modules/globalip.py @@ -12,13 +12,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import NetworkManager -from SoftLayer.CLI import ( - CLIRunnable, Table, confirm, no_going_back, resolve_id) -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class GlobalIpAssign(CLIRunnable): +class GlobalIpAssign(environment.CLIRunnable): """ usage: sl globalip assign [options] @@ -31,14 +32,14 @@ class GlobalIpAssign(CLIRunnable): action = 'assign' def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') mgr.assign_global_ip(global_ip_id, args['']) -class GlobalIpCancel(CLIRunnable): +class GlobalIpCancel(environment.CLIRunnable): """ usage: sl globalip cancel [options] @@ -49,18 +50,18 @@ class GlobalIpCancel(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') - if args['--really'] or no_going_back(global_ip_id): + if args['--really'] or formatting.no_going_back(global_ip_id): mgr.cancel_global_ip(global_ip_id) else: - raise CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class GlobalIpCreate(CLIRunnable): +class GlobalIpCreate(environment.CLIRunnable): """ usage: sl globalip create [options] @@ -75,19 +76,19 @@ class GlobalIpCreate(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) version = 4 if args.get('--v6'): version = 6 if not args.get('--test') and not args['--really']: - if not confirm("This action will incur charges on your account." - "Continue?"): - raise CLIAbort('Cancelling order.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Cancelling order.') result = mgr.add_global_ip(version=version, test_order=args.get('--test')) - table = Table(['Item', 'cost']) + table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -102,7 +103,7 @@ def execute(self, args): return table -class GlobalIpList(CLIRunnable): +class GlobalIpList(environment.CLIRunnable): """ usage: sl globalip list [options] @@ -115,9 +116,9 @@ class GlobalIpList(CLIRunnable): action = 'list' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) - table = Table(['id', 'ip', 'assigned', 'target']) + table = formatting.Table(['id', 'ip', 'assigned', 'target']) table.sortby = args.get('--sortby') or 'id' version = 0 @@ -150,7 +151,7 @@ def execute(self, args): return table -class GlobalIpUnassign(CLIRunnable): +class GlobalIpUnassign(environment.CLIRunnable): """ usage: sl globalip unassign [options] @@ -162,8 +163,8 @@ class GlobalIpUnassign(CLIRunnable): action = 'unassign' def execute(self, args): - mgr = NetworkManager(self.client) - global_ip_id = resolve_id(mgr.resolve_global_ip_ids, - args.get(''), - name='global ip') + mgr = SoftLayer.NetworkManager(self.client) + global_ip_id = helpers.resolve_id(mgr.resolve_global_ip_ids, + args.get(''), + name='global ip') mgr.unassign_global_ip(global_ip_id) diff --git a/SoftLayer/CLI/modules/help.py b/SoftLayer/CLI/modules/help.py index 2bf5fae83..0866687e1 100644 --- a/SoftLayer/CLI/modules/help.py +++ b/SoftLayer/CLI/modules/help.py @@ -9,17 +9,17 @@ # Missing docstrings ignored due to __doc__ = __doc__ magic # pylint: disable=C0111 -from SoftLayer.CLI.core import CommandParser -from SoftLayer.CLI import CLIRunnable +from SoftLayer.CLI import core +from SoftLayer.CLI import environment -class Show(CLIRunnable): +class Show(environment.CLIRunnable): # Use the same documentation as the module __doc__ = __doc__ action = None def execute(self, args): - parser = CommandParser(self.env) + parser = core.CommandParser(self.env) if not any([args[''], args['']]): return parser.get_module_help('help') diff --git a/SoftLayer/CLI/modules/image.py b/SoftLayer/CLI/modules/image.py index 099893ba2..7223749f9 100644 --- a/SoftLayer/CLI/modules/image.py +++ b/SoftLayer/CLI/modules/image.py @@ -11,12 +11,14 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import ImageManager -from SoftLayer.CLI import CLIRunnable, Table, KeyValueTable, blank, resolve_id -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class ListImages(CLIRunnable): +class ListImages(environment.CLIRunnable): """ usage: sl image list [--public | --private] [options] @@ -29,7 +31,7 @@ class ListImages(CLIRunnable): action = 'list' def execute(self, args): - image_mgr = ImageManager(self.client) + image_mgr = SoftLayer.ImageManager(self.client) neither = not any([args['--private'], args['--public']]) mask = 'id,accountId,name,globalIdentifier,blockDevices,parentId' @@ -45,26 +47,26 @@ def execute(self, args): image['visibility'] = 'public' images.append(image) - table = Table(['id', - 'account', - 'visibility', - 'name', - 'global_identifier']) + table = formatting.Table(['id', + 'account', + 'visibility', + 'name', + 'global_identifier']) images = [image for image in images if image['parentId'] == ''] for image in images: table.add_row([ image['id'], - image.get('accountId', blank()), + image.get('accountId', formatting.blank()), image['visibility'], image['name'].strip(), - image.get('globalIdentifier', blank()), + image.get('globalIdentifier', formatting.blank()), ]) return table -class DetailImage(CLIRunnable): +class DetailImage(environment.CLIRunnable): """ usage: sl image detail [options] @@ -73,27 +75,27 @@ class DetailImage(CLIRunnable): action = 'detail' def execute(self, args): - image_mgr = ImageManager(self.client) - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') + image_mgr = SoftLayer.ImageManager(self.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), + 'image') image = image_mgr.get_image(image_id) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['id', image['id']]) - table.add_row(['account', image.get('accountId', blank())]) + table.add_row(['account', image.get('accountId', formatting.blank())]) table.add_row(['name', image['name'].strip()]) table.add_row(['global_identifier', - image.get('globalIdentifier', blank())]) + image.get('globalIdentifier', formatting.blank())]) return table -class DeleteImage(CLIRunnable): +class DeleteImage(environment.CLIRunnable): """ usage: sl image delete [options] @@ -102,15 +104,15 @@ class DeleteImage(CLIRunnable): action = 'delete' def execute(self, args): - image_mgr = ImageManager(self.client) - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), - 'image') + image_mgr = SoftLayer.ImageManager(self.client) + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), + 'image') image_mgr.delete_image(image_id) -class EditImage(CLIRunnable): +class EditImage(environment.CLIRunnable): """ usage: sl image edit [--tag=Tag...] [options] @@ -126,7 +128,7 @@ class EditImage(CLIRunnable): action = 'edit' def execute(self, args): - image_mgr = ImageManager(self.client) + image_mgr = SoftLayer.ImageManager(self.client) data = {} if args.get('--name'): data['name'] = args.get('--name') @@ -134,7 +136,7 @@ def execute(self, args): data['note'] = args.get('--note') if args.get('--tag'): data['tag'] = args.get('--tag') - image_id = resolve_id(image_mgr.resolve_ids, - args.get(''), 'image') + image_id = helpers.resolve_id(image_mgr.resolve_ids, + args.get(''), 'image') if not image_mgr.edit(image_id, **data): - raise CLIAbort("Failed to Edit Image") + raise exceptions.CLIAbort("Failed to Edit Image") diff --git a/SoftLayer/CLI/modules/iscsi.py b/SoftLayer/CLI/modules/iscsi.py index cc916f98d..a0d5bbd41 100644 --- a/SoftLayer/CLI/modules/iscsi.py +++ b/SoftLayer/CLI/modules/iscsi.py @@ -12,13 +12,16 @@ For several commands, will be asked for. This will be the id for iSCSI target. """ -from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) -from SoftLayer.CLI.helpers import ( - CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable) -from SoftLayer import ISCSIManager +# from SoftLayer.CLI import (CLIRunnable, Table, no_going_back, FormattedItem) +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 -class ListISCSIs(CLIRunnable): +class ListISCSIs(environment.CLIRunnable): """ usage: sl iscsi list [options] @@ -28,10 +31,10 @@ class ListISCSIs(CLIRunnable): action = 'list' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) iscsi_list = iscsi_mgr.list_iscsi() - iscsi_list = [NestedDict(n) for n in iscsi_list] - table = Table([ + iscsi_list = [utils.NestedDict(n) for n in iscsi_list] + table = formatting.Table([ 'id', 'datacenter', 'size', @@ -42,17 +45,19 @@ def execute(self, args): for iscsi in iscsi_list: table.add_row([ iscsi['id'], - iscsi['serviceResource']['datacenter'].get('name', blank()), - FormattedItem( - iscsi.get('capacityGb', blank()), + iscsi['serviceResource']['datacenter'].get('name', + formatting.blank()), + formatting.FormattedItem( + iscsi.get('capacityGb', formatting.blank()), "%dGB" % iscsi.get('capacityGb', 0)), - iscsi.get('username', blank()), - iscsi.get('password', blank()), - iscsi.get('serviceResourceBackendIpAddress', blank())]) + iscsi.get('username', formatting.blank()), + iscsi.get('password', formatting.blank()), + iscsi.get('serviceResourceBackendIpAddress', + formatting.blank())]) return table -class CreateISCSI(CLIRunnable): +class CreateISCSI(environment.CLIRunnable): """ usage: sl iscsi create [options] @@ -73,7 +78,7 @@ class CreateISCSI(CLIRunnable): required_params = ['--size', '--datacenter'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) self._validate_create_args(args) size, location = self._parse_create_args(args) iscsi_mgr.create_iscsi(size=size, location=location) @@ -91,11 +96,11 @@ def _validate_create_args(self, args): """ Raises an ArgumentError if the given arguments are not valid """ invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) -class CancelISCSI(CLIRunnable): +class CancelISCSI(environment.CLIRunnable): """ usage: sl iscsi cancel [options] @@ -116,8 +121,8 @@ class CancelISCSI(CLIRunnable): options = ['confirm'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id( iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') @@ -125,13 +130,13 @@ def execute(self, args): immediate = args.get('--immediate', False) reason = args.get('--reason') - if args['--really'] or no_going_back(iscsi_id): + if args['--really'] or formatting.no_going_back(iscsi_id): iscsi_mgr.cancel_iscsi(iscsi_id, reason, immediate) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class ISCSIDetails(CLIRunnable): +class ISCSIDetails(environment.CLIRunnable): """ usage: sl iscsi detail [--password] [options] @@ -148,17 +153,17 @@ class ISCSIDetails(CLIRunnable): action = 'detail' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - table = KeyValueTable(['Name', 'Value']) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' - iscsi_id = resolve_id( + iscsi_id = helpers.resolve_id( iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') result = iscsi_mgr.get_iscsi(iscsi_id) - result = NestedDict(result) + result = utils.NestedDict(result) table.add_row(['id', result['id']]) table.add_row(['serviceResourceName', result['serviceResourceName']]) @@ -177,7 +182,7 @@ def execute(self, args): table.add_row(['notes', result['notes']]) if args.get('--password'): - pass_table = Table(['username', 'password']) + pass_table = formatting.Table(['username', 'password']) pass_table.add_row([result['username'], result['password']]) table.add_row(['users', pass_table]) diff --git a/SoftLayer/CLI/modules/loadbal.py b/SoftLayer/CLI/modules/loadbal.py index 2222832d4..cd1e46c00 100755 --- a/SoftLayer/CLI/modules/loadbal.py +++ b/SoftLayer/CLI/modules/loadbal.py @@ -23,10 +23,11 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import LoadBalancerManager -from SoftLayer.CLI import (CLIRunnable, Table, resolve_id, - confirm, KeyValueTable) -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers def get_ids(input_id): @@ -37,8 +38,8 @@ def get_ids(input_id): """ key_value = input_id.split(':') if len(key_value) != 2: - raise CLIAbort('Invalid ID %s: ID should be of the form xxx:yyy' - % input_id) + raise exceptions.CLIAbort( + 'Invalid ID %s: ID should be of the form xxx:yyy' % input_id) return key_value @@ -48,12 +49,12 @@ def get_local_lbs_table(load_balancers): :param dict load_balancers: A dictionary representing the load_balancers :returns: A table containing the local load balancers """ - table = Table(['ID', - 'VIP Address', - 'Location', - 'SSL Offload', - 'Connections/second', - 'Type']) + table = formatting.Table(['ID', + 'VIP Address', + 'Location', + 'SSL Offload', + 'Connections/second', + 'Type']) table.align['Connections/second'] = 'r' @@ -86,7 +87,7 @@ def get_local_lb_table(load_balancer): :param dict load_balancer: A dictionary representing the loadbal :returns: A table containing the local loadbal details """ - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'l' table.align['Value'] = 'l' table.add_row(['General properties', '----------']) @@ -104,8 +105,11 @@ def get_local_lb_table(load_balancer): table.add_row(['Service group %s' % index0, '**************']) index0 += 1 - table2 = Table(['Service group ID', 'Port', 'Allocation', - 'Routing type', 'Routing Method']) + table2 = formatting.Table(['Service group ID', + 'Port', + 'Allocation', + 'Routing type', + 'Routing Method']) for group in virtual_server['serviceGroups']: table2.add_row([ @@ -120,8 +124,13 @@ def get_local_lb_table(load_balancer): table.add_row([' Group Properties', table2]) - table3 = Table(['Service_ID', 'IP Address', 'Port', - 'Health Check', 'Weight', 'Enabled', 'Status']) + table3 = formatting.Table(['Service_ID', + 'IP Address', + 'Port', + 'Health Check', + 'Weight', + 'Enabled', + 'Status']) service_exist = False for service in group['services']: service_exist = True @@ -143,7 +152,7 @@ def get_local_lb_table(load_balancer): return table -class LoadBalancerList(CLIRunnable): +class LoadBalancerList(environment.CLIRunnable): """ usage: sl loadbal list [options] @@ -153,13 +162,13 @@ class LoadBalancerList(CLIRunnable): action = 'list' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) load_balancers = mgr.get_local_lbs() return get_local_lbs_table(load_balancers) -class LoadBalancerHealthChecks(CLIRunnable): +class LoadBalancerHealthChecks(environment.CLIRunnable): """ usage: sl loadbal health-checks [options] @@ -168,10 +177,10 @@ class LoadBalancerHealthChecks(CLIRunnable): action = 'health-checks' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) hc_types = mgr.get_hc_types() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -180,7 +189,7 @@ def execute(self, args): return table -class LoadBalancerRoutingMethods(CLIRunnable): +class LoadBalancerRoutingMethods(environment.CLIRunnable): """ usage: sl loadbal routing-methods [options] @@ -189,10 +198,10 @@ class LoadBalancerRoutingMethods(CLIRunnable): action = 'routing-methods' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) routing_methods = mgr.get_routing_methods() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -201,7 +210,7 @@ def execute(self, args): return table -class LoadBalancerRoutingTypes(CLIRunnable): +class LoadBalancerRoutingTypes(environment.CLIRunnable): """ usage: sl loadbal routing-types [options] @@ -210,10 +219,10 @@ class LoadBalancerRoutingTypes(CLIRunnable): action = 'routing-types' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) routing_types = mgr.get_routing_types() - table = KeyValueTable(['ID', 'Name']) + table = formatting.KeyValueTable(['ID', 'Name']) table.align['ID'] = 'l' table.align['Name'] = 'l' table.sortby = 'ID' @@ -222,7 +231,7 @@ def execute(self, args): return table -class LoadBalancerDetails(CLIRunnable): +class LoadBalancerDetails(environment.CLIRunnable): """ usage: sl loadbal detail [options] @@ -232,7 +241,7 @@ class LoadBalancerDetails(CLIRunnable): action = 'detail' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') @@ -243,7 +252,7 @@ def execute(self, args): return get_local_lb_table(load_balancer) -class LoadBalancerCancel(CLIRunnable): +class LoadBalancerCancel(environment.CLIRunnable): """ usage: sl loadbal cancel [options] @@ -254,21 +263,21 @@ class LoadBalancerCancel(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) loadbal_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a load " - "balancer. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "load balancer. Continue?"): mgr.cancel_lb(loadbal_id) return 'Load Balancer with id %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceDelete(CLIRunnable): +class LoadBalancerServiceDelete(environment.CLIRunnable): """ usage: sl loadbal service-delete [options] @@ -279,21 +288,22 @@ class LoadBalancerServiceDelete(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) service_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a service " - "from your load balancer. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "service from your load " + "balancer. Continue?"): mgr.delete_service(service_id) return 'Load balancer service %s is being cancelled!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceToggle(CLIRunnable): +class LoadBalancerServiceToggle(environment.CLIRunnable): """ usage: sl loadbal service-toggle [options] @@ -304,21 +314,22 @@ class LoadBalancerServiceToggle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) service_id = int(key_value[1]) - if args['--really'] or confirm("This action will toggle the service " - "status on the service. Continue?"): + if args['--really'] or formatting.confirm("This action will toggle " + "the status on the service. " + "Continue?"): mgr.toggle_service_status(service_id) return 'Load balancer service %s status updated!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceEdit(CLIRunnable): +class LoadBalancerServiceEdit(environment.CLIRunnable): """ usage: sl loadbal service-edit [options] @@ -334,7 +345,7 @@ class LoadBalancerServiceEdit(CLIRunnable): action = 'service-edit' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -365,7 +376,7 @@ def execute(self, args): return 'Load balancer service %s is being modified!' % input_id -class LoadBalancerServiceAdd(CLIRunnable): +class LoadBalancerServiceAdd(environment.CLIRunnable): """ usage: sl loadbal service-add --ip=IP --port=PORT \ --weight=WEIGHT --hc_type=HCTYPE --enabled=ENABLED [options] @@ -382,7 +393,7 @@ class LoadBalancerServiceAdd(CLIRunnable): action = 'service-add' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -406,7 +417,7 @@ def execute(self, args): return 'Load balancer service is being added!' -class LoadBalancerServiceGroupDelete(CLIRunnable): +class LoadBalancerServiceGroupDelete(environment.CLIRunnable): """ usage: sl loadbal group-delete [options] @@ -417,21 +428,21 @@ class LoadBalancerServiceGroupDelete(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) group_id = int(key_value[1]) - if args['--really'] or confirm("This action will cancel a service" - " group. Continue?"): + if args['--really'] or formatting.confirm("This action will cancel a " + "service group. Continue?"): mgr.delete_service_group(group_id) return 'Service group %s is being deleted!' % input_id else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class LoadBalancerServiceGroupEdit(CLIRunnable): +class LoadBalancerServiceGroupEdit(environment.CLIRunnable): """ usage: sl loadbal group-edit [options] @@ -446,7 +457,7 @@ class LoadBalancerServiceGroupEdit(CLIRunnable): action = 'group-edit' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -471,7 +482,7 @@ def execute(self, args): return 'Load balancer service group %s is being updated!' % input_id -class LoadBalancerServiceGroupReset(CLIRunnable): +class LoadBalancerServiceGroupReset(environment.CLIRunnable): """ usage: sl loadbal group-reset [options] @@ -481,7 +492,7 @@ class LoadBalancerServiceGroupReset(CLIRunnable): action = 'group-reset' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -492,7 +503,7 @@ def execute(self, args): return 'Load balancer service group connections are being reset!' -class LoadBalancerServiceGroupAdd(CLIRunnable): +class LoadBalancerServiceGroupAdd(environment.CLIRunnable): """ usage: sl loadbal group-add --allocation=PERC --port=PORT \ --routing_type=TYPE --routing_method=METHOD [options] @@ -508,7 +519,7 @@ class LoadBalancerServiceGroupAdd(CLIRunnable): action = 'group-add' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) input_id = args.get('') key_value = get_ids(input_id) @@ -523,7 +534,7 @@ def execute(self, args): return 'Load balancer service group is being added!' -class LoadBalancerCreate(CLIRunnable): +class LoadBalancerCreate(environment.CLIRunnable): """ usage: sl loadbal create (--datacenter=DC) [options] @@ -538,17 +549,17 @@ class LoadBalancerCreate(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = LoadBalancerManager(self.client) - input_id = resolve_id( + mgr = SoftLayer.LoadBalancerManager(self.client) + input_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'load_balancer') - if not confirm("This action will incur charges on your account. " - "Continue?"): - raise CLIAbort('Aborted.') + if not formatting.confirm("This action will incur charges on your " + "account. Continue?"): + raise exceptions.CLIAbort('Aborted.') mgr.add_local_lb(input_id, datacenter=args['--datacenter']) return "Load balancer is being created!" -class CreateOptionsLoadBalancer(CLIRunnable): +class CreateOptionsLoadBalancer(environment.CLIRunnable): """ usage: sl loadbal create-options @@ -558,9 +569,9 @@ class CreateOptionsLoadBalancer(CLIRunnable): action = 'create-options' def execute(self, args): - mgr = LoadBalancerManager(self.client) + mgr = SoftLayer.LoadBalancerManager(self.client) - table = Table(['id', 'capacity', 'description', 'price']) + table = formatting.Table(['id', 'capacity', 'description', 'price']) table.sortby = 'price' table.align['price'] = 'r' diff --git a/SoftLayer/CLI/modules/messaging.py b/SoftLayer/CLI/modules/messaging.py index 625466f8d..0c39a8ad0 100644 --- a/SoftLayer/CLI/modules/messaging.py +++ b/SoftLayer/CLI/modules/messaging.py @@ -31,9 +31,10 @@ # pylint: disable=C0111 import sys -from SoftLayer import MessagingManager -from SoftLayer.CLI import CLIRunnable, Table -from SoftLayer.CLI.helpers import CLIAbort, listing, ArgumentError, blank +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting COMMON_MESSAGING_ARGS = """Service Options: @@ -42,7 +43,7 @@ """ -class ListAccounts(CLIRunnable): +class ListAccounts(environment.CLIRunnable): """ usage: sl messaging accounts-list [options] @@ -52,10 +53,10 @@ class ListAccounts(CLIRunnable): action = 'accounts-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) accounts = manager.list_accounts() - table = Table([ + table = formatting.Table([ 'id', 'name', 'status' ]) for account in accounts: @@ -71,7 +72,7 @@ def execute(self, args): return table -class ListEndpoints(CLIRunnable): +class ListEndpoints(environment.CLIRunnable): """ usage: sl messaging endpoints-list [options] @@ -81,23 +82,23 @@ class ListEndpoints(CLIRunnable): action = 'endpoints-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) regions = manager.get_endpoints() - table = Table([ + table = formatting.Table([ 'name', 'public', 'private' ]) for region, endpoints in regions.items(): table.add_row([ region, - endpoints.get('public') or blank(), - endpoints.get('private') or blank(), + endpoints.get('public') or formatting.blank(), + endpoints.get('private') or formatting.blank(), ]) return table -class Ping(CLIRunnable): +class Ping(environment.CLIRunnable): __doc__ = """ usage: sl messaging ping [options] @@ -107,25 +108,25 @@ class Ping(CLIRunnable): action = 'ping' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) okay = manager.ping( datacenter=args['--datacenter'], network=args['--network']) if okay: return 'OK' else: - CLIAbort('Ping failed') + exceptions.CLIAbort('Ping failed') def queue_table(queue): """ Returns a table with details about a queue """ - table = Table(['property', 'value']) + 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', listing(queue['tags'] or [])]) + 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 @@ -133,7 +134,7 @@ def queue_table(queue): def message_table(message): """ Returns a table with details about a message """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' @@ -147,18 +148,18 @@ def message_table(message): def topic_table(topic): """ Returns a table with details about a topic """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' table.add_row(['name', topic['name']]) - table.add_row(['tags', listing(topic['tags'] or [])]) + table.add_row(['tags', formatting.listing(topic['tags'] or [])]) return table def subscription_table(sub): """ Returns a table with details about a subscription """ - table = Table(['property', 'value']) + table = formatting.Table(['property', 'value']) table.align['property'] = 'r' table.align['value'] = 'l' @@ -169,7 +170,7 @@ def subscription_table(sub): return table -class QueueList(CLIRunnable): +class QueueList(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-list [options] @@ -179,12 +180,12 @@ class QueueList(CLIRunnable): action = 'queue-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) queues = mq_client.get_queues()['items'] - table = Table([ + table = formatting.Table([ 'name', 'message_count', 'visible_message_count' ]) for queue in queues: @@ -196,7 +197,7 @@ def execute(self, args): return table -class QueueDetail(CLIRunnable): +class QueueDetail(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-detail [options] @@ -206,13 +207,13 @@ class QueueDetail(CLIRunnable): action = 'queue-detail' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) queue = mq_client.get_queue(args['']) return queue_table(queue) -class QueueCreate(CLIRunnable): +class QueueCreate(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-add [options] @@ -228,7 +229,7 @@ class QueueCreate(CLIRunnable): action = 'queue-add' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -243,7 +244,7 @@ def execute(self, args): return queue_table(queue) -class QueueModify(CLIRunnable): +class QueueModify(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-edit [options] @@ -259,7 +260,7 @@ class QueueModify(CLIRunnable): action = 'queue-edit' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -274,7 +275,7 @@ def execute(self, args): return queue_table(queue) -class QueueDelete(CLIRunnable): +class QueueDelete(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-remove [] [options] @@ -288,7 +289,7 @@ class QueueDelete(CLIRunnable): action = 'queue-remove' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) if args['']: @@ -298,7 +299,7 @@ def execute(self, args): mq_client.delete_queue(args[''], args.get('--force')) -class QueuePush(CLIRunnable): +class QueuePush(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-push ( | -) [options] @@ -312,7 +313,7 @@ class QueuePush(CLIRunnable): action = 'queue-push' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) body = '' if args[''] == '-': @@ -323,7 +324,7 @@ def execute(self, args): mq_client.push_queue_message(args[''], body)) -class QueuePop(CLIRunnable): +class QueuePop(environment.CLIRunnable): __doc__ = """ usage: sl messaging queue-pop [options] @@ -337,7 +338,7 @@ class QueuePop(CLIRunnable): action = 'queue-pop' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) messages = mq_client.pop_messages( @@ -355,7 +356,7 @@ def execute(self, args): return formatted_messages -class TopicList(CLIRunnable): +class TopicList(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-list [options] @@ -365,17 +366,17 @@ class TopicList(CLIRunnable): action = 'topic-list' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) topics = mq_client.get_topics()['items'] - table = Table(['name']) + table = formatting.Table(['name']) for topic in topics: table.add_row([topic['name']]) return table -class TopicDetail(CLIRunnable): +class TopicDetail(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-detail [options] @@ -385,7 +386,7 @@ class TopicDetail(CLIRunnable): action = 'topic-detail' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) topic = mq_client.get_topic(args['']) subscriptions = mq_client.get_subscriptions(args['']) @@ -395,7 +396,7 @@ def execute(self, args): return [topic_table(topic), tables] -class TopicCreate(CLIRunnable): +class TopicCreate(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-add [options] @@ -405,7 +406,7 @@ class TopicCreate(CLIRunnable): action = 'topic-add' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) tags = None if args.get('--tags'): @@ -421,7 +422,7 @@ def execute(self, args): return topic_table(topic) -class TopicDelete(CLIRunnable): +class TopicDelete(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-remove [options] @@ -434,12 +435,12 @@ class TopicDelete(CLIRunnable): action = 'topic-remove' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) mq_client.delete_topic(args[''], args.get('--force')) -class TopicSubscribe(CLIRunnable): +class TopicSubscribe(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-subscribe [options] @@ -456,7 +457,7 @@ class TopicSubscribe(CLIRunnable): action = 'topic-subscribe' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) if args['--type'] == 'queue': subscription = mq_client.create_subscription( @@ -473,12 +474,12 @@ def execute(self, args): body=args['--http-body'] ) else: - raise ArgumentError( + raise exceptions.ArgumentError( '--type should be either queue or http.') return subscription_table(subscription) -class TopicUnsubscribe(CLIRunnable): +class TopicUnsubscribe(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-unsubscribe [options] @@ -489,7 +490,7 @@ class TopicUnsubscribe(CLIRunnable): action = 'topic-unsubscribe' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) mq_client.delete_subscription( @@ -497,7 +498,7 @@ def execute(self, args): args['']) -class TopicPush(CLIRunnable): +class TopicPush(environment.CLIRunnable): __doc__ = """ usage: sl messaging topic-push ( | -) [options] @@ -508,7 +509,7 @@ class TopicPush(CLIRunnable): action = 'topic-push' def execute(self, args): - manager = MessagingManager(self.client) + manager = SoftLayer.MessagingManager(self.client) mq_client = manager.get_connection(args['']) # the message body comes from the positional argument or stdin diff --git a/SoftLayer/CLI/modules/metadata.py b/SoftLayer/CLI/modules/metadata.py index 8d0b8ccfa..82539d2de 100644 --- a/SoftLayer/CLI/modules/metadata.py +++ b/SoftLayer/CLI/modules/metadata.py @@ -22,18 +22,21 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import MetadataManager, TransportError -from SoftLayer.CLI import CLIRunnable, KeyValueTable, listing, CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -class MetaRunnable(CLIRunnable): +class MetaRunnable(environment.CLIRunnable): """ A CLIRunnable that raises a nice error on connection issues because - the metadata service is only accessable on a SoftLayer device """ + the metadata service is only accessable on a SoftLayer device + """ def execute(self, args): try: return self._execute(args) - except TransportError: - raise CLIAbort( + 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.') @@ -52,7 +55,8 @@ class BackendMacAddresses(MetaRunnable): action = 'backend_mac' def _execute(self, _): - return listing(MetadataManager().get('backend_mac'), separator=',') + backend_macs = SoftLayer.MetadataManager().get('backend_mac') + return formatting.listing(backend_macs, separator=',') class Datacenter(MetaRunnable): @@ -64,7 +68,7 @@ class Datacenter(MetaRunnable): action = 'datacenter' def _execute(self, _): - return MetadataManager().get('datacenter') + return SoftLayer.MetadataManager().get('datacenter') class DatacenterId(MetaRunnable): @@ -76,7 +80,7 @@ class DatacenterId(MetaRunnable): action = 'datacenter_id' def _execute(self, _): - return MetadataManager().get('datacenter_id') + return SoftLayer.MetadataManager().get('datacenter_id') class FrontendMacAddresses(MetaRunnable): @@ -88,7 +92,8 @@ class FrontendMacAddresses(MetaRunnable): action = 'frontend_mac' def _execute(self, _): - return listing(MetadataManager().get('frontend_mac'), separator=',') + frontend_macs = SoftLayer.MetadataManager().get('frontend_mac') + return formatting.listing(frontend_macs, separator=',') class FullyQualifiedDomainName(MetaRunnable): @@ -100,7 +105,7 @@ class FullyQualifiedDomainName(MetaRunnable): action = 'fqdn' def _execute(self, _): - return MetadataManager().get('fqdn') + return SoftLayer.MetadataManager().get('fqdn') class Hostname(MetaRunnable): @@ -112,7 +117,7 @@ class Hostname(MetaRunnable): action = 'hostname' def _execute(self, _): - return MetadataManager().get('hostname') + return SoftLayer.MetadataManager().get('hostname') class Id(MetaRunnable): @@ -124,7 +129,7 @@ class Id(MetaRunnable): action = 'id' def _execute(self, _): - return MetadataManager().get('id') + return SoftLayer.MetadataManager().get('id') class PrimaryBackendIpAddress(MetaRunnable): @@ -136,7 +141,7 @@ class PrimaryBackendIpAddress(MetaRunnable): action = 'backend_ip' def _execute(self, _): - return MetadataManager().get('primary_backend_ip') + return SoftLayer.MetadataManager().get('primary_backend_ip') class PrimaryIpAddress(MetaRunnable): @@ -148,7 +153,7 @@ class PrimaryIpAddress(MetaRunnable): action = 'ip' def _execute(self, _): - return MetadataManager().get('primary_ip') + return SoftLayer.MetadataManager().get('primary_ip') class ProvisionState(MetaRunnable): @@ -160,7 +165,7 @@ class ProvisionState(MetaRunnable): action = 'provision_state' def _execute(self, _): - return MetadataManager().get('provision_state') + return SoftLayer.MetadataManager().get('provision_state') class Tags(MetaRunnable): @@ -172,10 +177,11 @@ class Tags(MetaRunnable): action = 'tags' def _execute(self, _): - return listing(MetadataManager().get('tags'), separator=',') + return formatting.listing(SoftLayer.MetadataManager().get('tags'), + separator=',') -class UserMetadata(CLIRunnable): +class UserMetadata(environment.CLIRunnable): """ usage: sl metadata user_data [options] @@ -185,11 +191,11 @@ class UserMetadata(CLIRunnable): def _execute(self, _): """ Returns user metadata """ - userdata = MetadataManager().get('user_data') + userdata = SoftLayer.MetadataManager().get('user_data') if userdata: return userdata else: - raise CLIAbort("No user metadata.") + raise exceptions.CLIAbort("No user metadata.") class Network(MetaRunnable): @@ -201,37 +207,37 @@ class Network(MetaRunnable): action = 'network' def _execute(self, args): - meta = MetadataManager() + meta = SoftLayer.MetadataManager() if args['']: - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' network = meta.public_network() table.add_row([ 'mac addresses', - listing(network['mac_addresses'], separator=',')]) + formatting.listing(network['mac_addresses'], separator=',')]) table.add_row([ 'router', network['router']]) table.add_row([ - 'vlans', listing(network['vlans'], separator=',')]) + 'vlans', formatting.listing(network['vlans'], separator=',')]) table.add_row([ 'vlan ids', - listing(network['vlan_ids'], separator=',')]) + formatting.listing(network['vlan_ids'], separator=',')]) return table if args['']: - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' network = meta.private_network() table.add_row([ 'mac addresses', - listing(network['mac_addresses'], separator=',')]) + formatting.listing(network['mac_addresses'], separator=',')]) table.add_row([ 'router', network['router']]) table.add_row([ - 'vlans', listing(network['vlans'], separator=',')]) + 'vlans', formatting.listing(network['vlans'], separator=',')]) table.add_row([ 'vlan ids', - listing(network['vlan_ids'], separator=',')]) + formatting.listing(network['vlan_ids'], separator=',')]) return table diff --git a/SoftLayer/CLI/modules/nas.py b/SoftLayer/CLI/modules/nas.py index cfc59804e..860e7bf09 100644 --- a/SoftLayer/CLI/modules/nas.py +++ b/SoftLayer/CLI/modules/nas.py @@ -8,12 +8,12 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer.CLI import CLIRunnable, Table, FormattedItem -from SoftLayer.CLI.helpers import blank -from SoftLayer.utils import lookup +from SoftLayer.CLI import environment +from SoftLayer.CLI import formatting +from SoftLayer import utils -class ListNAS(CLIRunnable): +class ListNAS(environment.CLIRunnable): """ usage: sl nas list [options] @@ -29,21 +29,22 @@ def execute(self, args): nas_accounts = account.getNasNetworkStorage( mask='eventCount,serviceResource[datacenter.name]') - table = Table(['id', 'datacenter', 'size', 'username', 'password', - 'server']) + table = formatting.Table(['id', 'datacenter', 'size', 'username', + 'password', 'server']) for nas_account in nas_accounts: table.add_row([ nas_account['id'], - lookup(nas_account, - 'serviceResource', - 'datacenter', - 'name') or blank(), - FormattedItem( - nas_account.get('capacityGb', blank()), + utils.lookup(nas_account, + 'serviceResource', + 'datacenter', + 'name') or formatting.blank(), + formatting.FormattedItem( + nas_account.get('capacityGb', formatting.blank()), "%dGB" % nas_account.get('capacityGb', 0)), - nas_account.get('username', blank()), - nas_account.get('password', blank()), - nas_account.get('serviceResourceBackendIpAddress', blank())]) + nas_account.get('username', formatting.blank()), + nas_account.get('password', formatting.blank()), + nas_account.get('serviceResourceBackendIpAddress', + formatting.blank())]) return table diff --git a/SoftLayer/CLI/modules/rwhois.py b/SoftLayer/CLI/modules/rwhois.py index 810cf2365..c036488a0 100644 --- a/SoftLayer/CLI/modules/rwhois.py +++ b/SoftLayer/CLI/modules/rwhois.py @@ -9,12 +9,13 @@ """ # :license: MIT, see LICENSE for more details. -from SoftLayer import NetworkManager -from SoftLayer.CLI import CLIRunnable, KeyValueTable -from SoftLayer.CLI.helpers import CLIAbort +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting -class RWhoisEdit(CLIRunnable): +class RWhoisEdit(environment.CLIRunnable): """ usage: sl rwhois edit [options] @@ -40,7 +41,7 @@ class RWhoisEdit(CLIRunnable): action = 'edit' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) update = { 'abuse_email': args.get('--abuse'), @@ -62,12 +63,13 @@ def execute(self, args): check = [x for x in update.values() if x is not None] if not check: - raise CLIAbort("You must specify at least one field to update.") + raise exceptions.CLIAbort( + "You must specify at least one field to update.") mgr.edit_rwhois(**update) # pylint: disable=W0142 -class RWhoisShow(CLIRunnable): +class RWhoisShow(environment.CLIRunnable): """ usage: sl rwhois show [options] @@ -76,10 +78,10 @@ class RWhoisShow(CLIRunnable): action = 'show' def execute(self, args): - mgr = NetworkManager(self.client) + mgr = SoftLayer.NetworkManager(self.client) result = mgr.get_rwhois() - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' table.add_row(['Name', result['firstName'] + ' ' + result['lastName']]) diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index b2e16852e..2ff78ca2c 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -23,17 +23,19 @@ hostname or the ip address for a piece of hardware. """ # :license: MIT, see LICENSE for more details. -import re import os -from os import linesep -from SoftLayer.CLI.helpers import ( - CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, - blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, - ArgumentError, update_with_template_args, export_to_template) -from SoftLayer import HardwareManager, SshKeyManager +import re +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers +from SoftLayer.CLI import template +from SoftLayer import utils -class ListServers(CLIRunnable): + +class ListServers(environment.CLIRunnable): """ usage: sl server list [options] @@ -63,7 +65,7 @@ class ListServers(CLIRunnable): action = 'list' def execute(self, args): - manager = HardwareManager(self.client) + manager = SoftLayer.HardwareManager(self.client) tags = None if args.get('--tags'): @@ -78,7 +80,7 @@ def execute(self, args): nic_speed=args.get('--network'), tags=tags) - table = Table([ + table = formatting.Table([ 'id', 'datacenter', 'host', @@ -91,22 +93,22 @@ def execute(self, args): table.sortby = args.get('--sortby') or 'host' for server in servers: - server = NestedDict(server) + server = utils.NestedDict(server) table.add_row([ server['id'], - server['datacenter']['name'] or blank(), + server['datacenter']['name'] or formatting.blank(), server['fullyQualifiedDomainName'], server['processorPhysicalCoreAmount'], - gb(server['memoryCapacity'] or 0), - server['primaryIpAddress'] or blank(), - server['primaryBackendIpAddress'] or blank(), - active_txn(server), + formatting.gb(server['memoryCapacity'] or 0), + server['primaryIpAddress'] or formatting.blank(), + server['primaryBackendIpAddress'] or formatting.blank(), + formatting.active_txn(server), ]) return table -class ServerDetails(CLIRunnable): +class ServerDetails(environment.CLIRunnable): """ usage: sl server detail [--passwords] [--price] [options] @@ -119,39 +121,45 @@ class ServerDetails(CLIRunnable): action = 'detail' def execute(self, args): - hardware = HardwareManager(self.client) + hardware = SoftLayer.HardwareManager(self.client) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' - hardware_id = resolve_id( + hardware_id = helpers.resolve_id( hardware.resolve_ids, args.get(''), 'hardware') result = hardware.get_hardware(hardware_id) - result = NestedDict(result) + result = utils.NestedDict(result) table.add_row(['id', result['id']]) table.add_row(['hostname', result['fullyQualifiedDomainName']]) table.add_row(['status', result['hardwareStatus']['status']]) - table.add_row(['datacenter', result['datacenter']['name'] or blank()]) + table.add_row(['datacenter', + result['datacenter']['name'] or formatting.blank()]) table.add_row(['cores', result['processorPhysicalCoreAmount']]) - table.add_row(['memory', gb(result['memoryCapacity'])]) - table.add_row(['public_ip', result['primaryIpAddress'] or blank()]) - table.add_row( - ['private_ip', result['primaryBackendIpAddress'] or blank()]) + table.add_row(['memory', + formatting.gb(result['memoryCapacity'])]) + 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 blank()]) + result['networkManagementIpAddress'] + or formatting.blank()]) table.add_row([ 'os', - FormattedItem( + formatting.FormattedItem( result['operatingSystem']['softwareLicense'] - ['softwareDescription']['referenceCode'] or blank(), + ['softwareDescription']['referenceCode'] or formatting.blank(), result['operatingSystem']['softwareLicense'] - ['softwareDescription']['name'] or blank() + ['softwareDescription']['name'] or formatting.blank() )]) - table.add_row(['created', result['provisionDate'] or blank()]) + table.add_row(['created', + result['provisionDate'] or formatting.blank()]) - vlan_table = Table(['type', 'number', 'id']) + vlan_table = formatting.Table(['type', 'number', 'id']) for vlan in result['networkVlans']: vlan_table.add_row([ vlan['networkSpace'], vlan['vlanNumber'], vlan['id']]) @@ -169,19 +177,19 @@ def execute(self, args): for item in result['operatingSystem']['passwords']: user_strs.append( "%s %s" % (item['username'], item['password'])) - table.add_row(['users', listing(user_strs)]) + table.add_row(['users', formatting.listing(user_strs)]) tag_row = [] for tag in result['tagReferences']: tag_row.append(tag['tag']['name']) if tag_row: - table.add_row(['tags', listing(tag_row, separator=',')]) + table.add_row(['tags', formatting.listing(tag_row, separator=',')]) # Test to see if this actually has a primary (public) ip address if result['primaryIpAddress']: - ptr_domains = self.client['Hardware_Server']\ - .getReverseDomainRecords(id=hardware_id) + ptr_domains = (self.client['Hardware_Server'] + .getReverseDomainRecords(id=hardware_id)) for ptr_domain in ptr_domains: for ptr in ptr_domain['resourceRecords']: @@ -190,7 +198,7 @@ def execute(self, args): return table -class ServerReload(CLIRunnable): +class ServerReload(environment.CLIRunnable): """ usage: sl server reload [--key=KEY...] [options] @@ -207,22 +215,22 @@ class ServerReload(CLIRunnable): options = ['confirm'] def execute(self, args): - hardware = HardwareManager(self.client) - hardware_id = resolve_id( + hardware = SoftLayer.HardwareManager(self.client) + hardware_id = helpers.resolve_id( hardware.resolve_ids, args.get(''), 'hardware') keys = [] if args.get('--key'): for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(self.client).resolve_ids, - key, 'SshKey') + resolver = SoftLayer.SshKeyManager(self.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') keys.append(key_id) - if args['--really'] or no_going_back(hardware_id): + if args['--really'] or formatting.no_going_back(hardware_id): hardware.reload(hardware_id, args['--postinstall'], keys) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class CancelServer(CLIRunnable): +class CancelServer(environment.CLIRunnable): """ usage: sl server cancel [options] @@ -238,8 +246,8 @@ class CancelServer(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id( + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id( mgr.resolve_ids, args.get(''), 'hardware') comment = args.get('--comment') @@ -249,13 +257,13 @@ def execute(self, args): reason = args.get('--reason') - if args['--really'] or no_going_back(hw_id): + if args['--really'] or formatting.no_going_back(hw_id): mgr.cancel_hardware(hw_id, reason, comment) else: - CLIAbort('Aborted') + raise exceptions.CLIAbort('Aborted') -class ServerCancelReasons(CLIRunnable): +class ServerCancelReasons(environment.CLIRunnable): """ usage: sl server cancel-reasons @@ -265,11 +273,11 @@ class ServerCancelReasons(CLIRunnable): action = 'cancel-reasons' def execute(self, args): - table = Table(['Code', 'Reason']) + table = formatting.Table(['Code', 'Reason']) table.align['Code'] = 'r' table.align['Reason'] = 'l' - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) for code, reason in mgr.get_cancellation_reasons().items(): table.add_row([code, reason]) @@ -277,7 +285,7 @@ def execute(self, args): return table -class ServerPowerOff(CLIRunnable): +class ServerPowerOff(environment.CLIRunnable): """ usage: sl server power-off [options] @@ -287,17 +295,19 @@ class ServerPowerOff(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s ' + 'Continue?' % hw_id): self.client['Hardware_Server'].powerOff(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class ServerReboot(CLIRunnable): +class ServerReboot(environment.CLIRunnable): """ usage: sl server reboot [--hard | --soft] [options] @@ -312,11 +322,13 @@ class ServerReboot(CLIRunnable): def execute(self, args): hardware_server = self.client['Hardware_Server'] - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s. ' + 'Continue?' % hw_id): if args['--hard']: hardware_server.rebootHard(id=hw_id) elif args['--soft']: @@ -324,10 +336,10 @@ def execute(self, args): else: hardware_server.rebootDefault(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class ServerPowerOn(CLIRunnable): +class ServerPowerOn(environment.CLIRunnable): """ usage: sl server power-on [options] @@ -336,13 +348,14 @@ class ServerPowerOn(CLIRunnable): action = 'power-on' def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') self.client['Hardware_Server'].powerOn(id=hw_id) -class ServerPowerCycle(CLIRunnable): +class ServerPowerCycle(environment.CLIRunnable): """ usage: sl server power-cycle [options] @@ -352,18 +365,20 @@ class ServerPowerCycle(CLIRunnable): options = ['confirm'] def execute(self, args): - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') - - if args['--really'] or confirm('This will power off the server with ' - 'id %s. Continue?' % hw_id): + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') + + if args['--really'] or formatting.confirm('This will power off the ' + 'server with id %s. ' + 'Continue?' % hw_id): self.client['Hardware_Server'].powerCycle(id=hw_id) else: - raise CLIAbort('Aborted.') + raise exceptions.CLIAbort('Aborted.') -class NicEditServer(CLIRunnable): +class NicEditServer(environment.CLIRunnable): """ usage: sl server nic-edit (public | private) --speed=SPEED [options] @@ -379,14 +394,15 @@ class NicEditServer(CLIRunnable): def execute(self, args): public = args['public'] - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') mgr.change_port_speed(hw_id, public, args['--speed']) -class ListChassisServer(CLIRunnable): +class ListChassisServer(environment.CLIRunnable): """ usage: sl server list-chassis [options] @@ -395,11 +411,11 @@ class ListChassisServer(CLIRunnable): action = 'list-chassis' def execute(self, args): - table = Table(['Code', 'Chassis']) + table = formatting.Table(['Code', 'Chassis']) table.align['Code'] = 'r' table.align['Chassis'] = 'l' - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) chassis = mgr.get_available_dedicated_server_packages() for chassis in chassis: @@ -408,7 +424,7 @@ def execute(self, args): return table -class ServerCreateOptions(CLIRunnable): +class ServerCreateOptions(environment.CLIRunnable): """ usage: sl server create-options [options] @@ -431,9 +447,9 @@ class ServerCreateOptions(CLIRunnable): 'controller'] def execute(self, args): - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) - table = KeyValueTable(['Name', 'Value']) + table = formatting.KeyValueTable(['Name', 'Value']) table.align['Name'] = 'r' table.align['Value'] = 'l' @@ -446,7 +462,7 @@ def execute(self, args): break if not found: - raise CLIAbort('Invalid chassis specified.') + raise exceptions.CLIAbort('Invalid chassis specified.') ds_options = mgr.get_dedicated_server_create_options(chassis_id) @@ -467,12 +483,12 @@ def execute(self, args): if args['--datacenter'] or show_all: results = self.get_create_options(ds_options, 'datacenter')[0] - table.add_row([results[0], listing(sorted(results[1]))]) + table.add_row([results[0], formatting.listing(sorted(results[1]))]) if (args['--cpu'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'cpu') - cpu_table = Table(['ID', 'Description']) + cpu_table = formatting.Table(['ID', 'Description']) cpu_table.align['ID'] = 'r' cpu_table.align['Description'] = 'l' @@ -483,16 +499,16 @@ def execute(self, args): if (args['--memory'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'memory')[0] - table.add_row([results[0], listing( + table.add_row([results[0], formatting.listing( item[0] for item in sorted(results[1]))]) if bmc and (show_all or args['--memory'] or args['--cpu']): results = self.get_create_options(ds_options, 'server_core') - memory_cpu_table = Table(['memory', 'cpu']) + memory_cpu_table = formatting.Table(['memory', 'cpu']) for result in results: memory_cpu_table.add_row([ result[0], - listing( + formatting.listing( [item[0] for item in sorted( result[1], key=lambda x: int(x[0]) )])]) @@ -504,9 +520,9 @@ def execute(self, args): for result in results: table.add_row([ result[0], - listing( + formatting.listing( [item[0] for item in sorted(result[1])], - separator=linesep + separator=os.linesep )]) if args['--disk'] or show_all: @@ -514,22 +530,22 @@ def execute(self, args): table.add_row([ results[0], - listing( + formatting.listing( [item[0] for item in sorted(results[1])], - separator=linesep + separator=os.linesep )]) if args['--nic'] or show_all: results = self.get_create_options(ds_options, 'nic') for result in results: - table.add_row([result[0], listing( + table.add_row([result[0], formatting.listing( item[0] for item in sorted(result[1],))]) if (args['--controller'] or show_all) and not bmc: results = self.get_create_options(ds_options, 'disk_controller')[0] - table.add_row([results[0], listing( + table.add_row([results[0], formatting.listing( item[0] for item in sorted(results[1],))]) return table @@ -568,8 +584,8 @@ def get_create_options(self, ds_options, section, pretty=True): ram.append((int(option['capacity']), option['price_id'])) return_value = [('memory', ram)] - elif 'server_core' == section and \ - 'server_core' in ds_options['categories']: + elif ('server_core' == section + and 'server_core' in ds_options['categories']): mem_options = {} cpu_regex = re.compile(r'(\d+) x ') memory_regex = re.compile(r' - (\d+) GB Ram', re.I) @@ -740,7 +756,7 @@ def _generate_windows_code(self, description): return os_code -class CreateServer(CLIRunnable): +class CreateServer(environment.CLIRunnable): """ usage: sl server create [--disk=SIZE...] [--key=KEY...] [options] @@ -784,8 +800,8 @@ class CreateServer(CLIRunnable): '--memory', '--os'] def execute(self, args): - update_with_template_args(args, list_args=['--disk', '--key']) - mgr = HardwareManager(self.client) + template.update_with_template_args(args, list_args=['--disk', '--key']) + mgr = SoftLayer.HardwareManager(self.client) self._validate_args(args) ds_options = mgr.get_dedicated_server_create_options(args['--chassis']) @@ -799,7 +815,7 @@ def execute(self, args): if args.get('--test'): result = mgr.verify_order(**order) - table = Table(['Item', 'cost']) + table = formatting.Table(['Item', 'cost']) table.align['Item'] = 'r' table.align['cost'] = 'r' @@ -813,30 +829,31 @@ def execute(self, args): table.add_row(['Total monthly cost', "%.2f" % total]) output = [] output.append(table) - output.append(FormattedItem( + output.append(formatting.FormattedItem( '', ' -- ! Prices reflected here are retail and do not ' 'take account level discounts and are not guaranteed.')) if args['--export']: export_file = args.pop('--export') - export_to_template(export_file, args, exclude=['--wait', '--test']) + template.export_to_template(export_file, args, + exclude=['--wait', '--test']) return 'Successfully exported options to a template file.' if do_create: - if args['--really'] or confirm( + if args['--really'] or formatting.confirm( "This action will incur charges on your account. " "Continue?"): result = mgr.place_order(**order) - table = KeyValueTable(['name', 'value']) + 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']]) output = table else: - raise CLIAbort('Aborting dedicated server order.') + raise exceptions.CLIAbort('Aborting dedicated server order.') return output @@ -845,7 +862,7 @@ def _process_args(self, args, ds_options): Helper method to centralize argument processing without convoluting code flow of the main execute method. """ - mgr = HardwareManager(self.client) + mgr = SoftLayer.HardwareManager(self.client) order = { 'hostname': args['--hostname'], @@ -866,7 +883,7 @@ def _process_args(self, args, ds_options): if os_price: order['os'] = os_price else: - raise CLIAbort('Invalid operating system specified.') + raise exceptions.CLIAbort('Invalid operating system specified.') order['location'] = args['--datacenter'] or 'FIRST_AVAILABLE' @@ -917,7 +934,7 @@ def _process_args(self, args, ds_options): if nic_price: order['port_speed'] = nic_price else: - raise CLIAbort('Invalid NIC speed specified.') + raise exceptions.CLIAbort('Invalid NIC speed specified.') if args.get('--postinstall'): order['post_uri'] = args.get('--postinstall') @@ -926,8 +943,8 @@ def _process_args(self, args, ds_options): if args.get('--key'): keys = [] for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(self.client).resolve_ids, - key, 'SshKey') + resolver = SoftLayer.SshKeyManager(self.client).resolve_ids + key_id = helpers.resolve_id(resolver, key, 'SshKey') keys.append(key_id) order['ssh_keys'] = keys @@ -943,8 +960,8 @@ def _validate_args(self, args): """ Raises an ArgumentError if the given arguments are not valid """ invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) def _get_default_value(self, ds_options, option): """ Returns a 'free' price id given an option """ @@ -1003,7 +1020,7 @@ def _get_price_id_from_options(self, ds_options, option, value, return item_options[2] -class EditServer(CLIRunnable): +class EditServer(environment.CLIRunnable): """ usage: sl server edit [options] @@ -1021,11 +1038,11 @@ def execute(self, args): data = {} if args['--userdata'] and args['--userfile']: - raise ArgumentError('[-u | --userdata] not allowed with ' - '[-F | --userfile]') + raise exceptions.ArgumentError( + '[-u | --userdata] not allowed with [-F | --userfile]') if args['--userfile']: if not os.path.exists(args['--userfile']): - raise ArgumentError( + raise exceptions.ArgumentError( 'File does not exist [-u | --userfile] = %s' % args['--userfile']) @@ -1038,8 +1055,9 @@ def execute(self, args): data['hostname'] = args.get('--hostname') data['domain'] = args.get('--domain') - mgr = HardwareManager(self.client) - hw_id = resolve_id(mgr.resolve_ids, args.get(''), - 'hardware') + mgr = SoftLayer.HardwareManager(self.client) + hw_id = helpers.resolve_id(mgr.resolve_ids, + args.get(''), + 'hardware') if not mgr.edit(hw_id, **data): - raise CLIAbort("Failed to update hardware") + raise exceptions.CLIAbort("Failed to update hardware") diff --git a/SoftLayer/CLI/modules/snapshot.py b/SoftLayer/CLI/modules/snapshot.py index 21bfeeee6..a7d95b3b5 100644 --- a/SoftLayer/CLI/modules/snapshot.py +++ b/SoftLayer/CLI/modules/snapshot.py @@ -13,14 +13,15 @@ For several commands will be asked for.This can be the id of iSCSI volume or iSCSI snapshot. """ -from SoftLayer.CLI import (CLIRunnable, Table) -from SoftLayer.CLI.helpers import ( - ArgumentError, NestedDict, - resolve_id) -from SoftLayer import ISCSIManager +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 -class CreateSnapshot(CLIRunnable): +class CreateSnapshot(environment.CLIRunnable): """ usage: sl snapshot create [options] @@ -38,16 +39,15 @@ class CreateSnapshot(CLIRunnable): action = 'create' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') notes = args.get('--notes') iscsi_mgr.create_snapshot(iscsi_id, notes) -class CreateSnapshotSpace(CLIRunnable): +class CreateSnapshotSpace(environment.CLIRunnable): """ usage: sl snapshot create-space [options] @@ -65,20 +65,19 @@ class CreateSnapshotSpace(CLIRunnable): required_params = ['--capacity'] def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) + iscsi_mgr = SoftLayer.ISCSIManager(self.client) invalid_args = [k for k in self.required_params if args.get(k) is None] if invalid_args: - raise ArgumentError('Missing required options: %s' - % ','.join(invalid_args)) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'iSCSI') + raise exceptions.ArgumentError('Missing required options: %s' + % ','.join(invalid_args)) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') capacity = args.get('--capacity') iscsi_mgr.create_snapshot_space(iscsi_id, capacity) -class CancelSnapshot(CLIRunnable): +class CancelSnapshot(environment.CLIRunnable): """ usage: sl snapshot cancel [options] @@ -89,15 +88,14 @@ class CancelSnapshot(CLIRunnable): action = 'cancel' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - snapshot_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'Snapshot') iscsi_mgr.delete_snapshot(snapshot_id) -class RestoreVolumeFromSnapshot(CLIRunnable): +class RestoreVolumeFromSnapshot(environment.CLIRunnable): """ usage: sl snapshot restore-volume @@ -108,17 +106,17 @@ class RestoreVolumeFromSnapshot(CLIRunnable): action = 'restore-volume' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - volume_id = resolve_id( - iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') - snapshot_id = resolve_id( - iscsi_mgr.resolve_ids, - args.get(''), - 'Snapshot') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + volume_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') + snapshot_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'Snapshot') iscsi_mgr.restore_from_snapshot(volume_id, snapshot_id) -class ListSnapshots(CLIRunnable): +class ListSnapshots(environment.CLIRunnable): """ usage: sl snapshot list @@ -128,15 +126,16 @@ class ListSnapshots(CLIRunnable): action = 'list' def execute(self, args): - iscsi_mgr = ISCSIManager(self.client) - iscsi_id = resolve_id( - iscsi_mgr.resolve_ids, args.get(''), 'iSCSI') + iscsi_mgr = SoftLayer.ISCSIManager(self.client) + iscsi_id = helpers.resolve_id(iscsi_mgr.resolve_ids, + args.get(''), + 'iSCSI') iscsi = self.client['Network_Storage_Iscsi'] snapshots = iscsi.getPartnerships( mask='volumeId,partnerVolumeId,createDate,type', id=iscsi_id) - snapshots = [NestedDict(n) for n in snapshots] + snapshots = [utils.NestedDict(n) for n in snapshots] - table = Table([ + table = formatting.Table([ 'id', 'createDate', 'name', @@ -149,5 +148,5 @@ def execute(self, args): snapshot['createDate'], snapshot['type']['name'], snapshot['type']['description'], - ]) + ]) return table diff --git a/SoftLayer/CLI/modules/sshkey.py b/SoftLayer/CLI/modules/sshkey.py index 88979191b..9649f355f 100644 --- a/SoftLayer/CLI/modules/sshkey.py +++ b/SoftLayer/CLI/modules/sshkey.py @@ -12,14 +12,16 @@ """ # :license: MIT, see LICENSE for more details. -from os.path import expanduser +from os import path -from SoftLayer import SshKeyManager -from SoftLayer.CLI import CLIRunnable, Table, no_going_back -from SoftLayer.CLI.helpers import CLIAbort, resolve_id, KeyValueTable +import SoftLayer +from SoftLayer.CLI import environment +from SoftLayer.CLI import exceptions +from SoftLayer.CLI import formatting +from SoftLayer.CLI import helpers -class AddSshKey(CLIRunnable): +class AddSshKey(environment.CLIRunnable): """ usage: sl sshkey add ''' - @patch('SoftLayer.transports.requests.Session.send') + @mock.patch('SoftLayer.transports.requests.Session.send') def test_call(self, send): send().content = self.send_content @@ -44,43 +44,44 @@ def test_call(self, send): ''' - resp = make_xml_rpc_api_call( + resp = transports.make_xml_rpc_api_call( 'http://something.com/path/to/resource', 'getObject') args = send.call_args self.assertIsNotNone(args) args, kwargs = args - send.assert_called_with(ANY, proxies=None, timeout=None) + send.assert_called_with(mock.ANY, proxies=None, timeout=None) self.assertEqual(resp, []) - self.assertEquals(args[0].body, data) + self.assertEqual(args[0].body, data) def test_proxy_without_protocol(self): self.assertRaises( - TransportError, - make_xml_rpc_api_call, + SoftLayer.TransportError, + transports.make_xml_rpc_api_call, 'http://something.com/path/to/resource', 'getObject', 'localhost:3128') - @patch('SoftLayer.transports.requests.Session.send') + @mock.patch('SoftLayer.transports.requests.Session.send') def test_valid_proxy(self, send): send().content = self.send_content - make_xml_rpc_api_call('http://something.com/path/to/resource', - 'getObject', - proxy='http://localhost:3128') + transports.make_xml_rpc_api_call( + 'http://something.com/path/to/resource', + 'getObject', + proxy='http://localhost:3128') send.assert_called_with( - ANY, + mock.ANY, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, timeout=None) -class TestRestAPICall(TestCase): +class TestRestAPICall(testing.TestCase): - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_json(self, request): request().content = '{}' - resp = make_rest_api_call( + resp = transports.make_rest_api_call( 'GET', 'http://something.com/path/to/resource.json') self.assertEqual(resp, {}) request.assert_called_with( @@ -90,8 +91,8 @@ def test_json(self, request): timeout=None) # Test JSON Error - e = HTTPError('error') - e.response = MagicMock() + e = requests.HTTPError('error') + e.response = mock.MagicMock() e.response.status_code = 404 e.response.content = '''{ "error": "description", @@ -100,35 +101,36 @@ def test_json(self, request): request().raise_for_status.side_effect = e self.assertRaises( - SoftLayerAPIError, - make_rest_api_call, + SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.json') def test_proxy_without_protocol(self): self.assertRaises( - TransportError, - make_rest_api_call, + SoftLayer.TransportError, + transports.make_rest_api_call, 'GET' 'http://something.com/path/to/resource.txt', 'localhost:3128') - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_valid_proxy(self, request): - make_rest_api_call('GET', - 'http://something.com/path/to/resource.txt', - proxy='http://localhost:3128') + transports.make_rest_api_call( + 'GET', + 'http://something.com/path/to/resource.txt', + proxy='http://localhost:3128') request.assert_called_with( 'GET', 'http://something.com/path/to/resource.txt', - headers=ANY, + headers=mock.ANY, proxies={'https': 'http://localhost:3128', 'http': 'http://localhost:3128'}, timeout=None) - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_text(self, request): request().text = 'content' - resp = make_rest_api_call( + resp = transports.make_rest_api_call( 'GET', 'http://something.com/path/to/resource.txt') self.assertEqual(resp, 'content') request.assert_called_with( @@ -138,28 +140,28 @@ def test_text(self, request): timeout=None) # Test Text Error - e = HTTPError('error') - e.response = MagicMock() + 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 self.assertRaises( - SoftLayerAPIError, - make_rest_api_call, + SoftLayer.SoftLayerAPIError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.txt') - @patch('SoftLayer.transports.requests.request') + @mock.patch('SoftLayer.transports.requests.request') def test_unknown_error(self, request): - e = RequestException('error') - e.response = MagicMock() + 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 self.assertRaises( - TransportError, - make_rest_api_call, + SoftLayer.TransportError, + transports.make_rest_api_call, 'GET', 'http://something.com/path/to/resource.txt') diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py index 5aa9f5e9c..364c60f24 100644 --- a/SoftLayer/transports.py +++ b/SoftLayer/transports.py @@ -5,21 +5,19 @@ :license: MIT, see LICENSE for more details. """ -from SoftLayer.exceptions import ( - SoftLayerAPIError, NotWellFormed, UnsupportedEncoding, InvalidCharacter, - SpecViolation, MethodNotFound, InvalidMethodParameters, InternalError, - ApplicationError, RemoteSystemError, TransportError) -from SoftLayer.utils import xmlrpc_client +from SoftLayer import exceptions +from SoftLayer import utils +import json import logging + import requests -import json LOGGER = logging.getLogger(__name__) def _proxies_dict(proxy): - """ Makes a dict appropriate to pass to requests """ + """Makes a dict appropriate to pass to requests.""" if not proxy: return None return {'http': proxy, 'https': proxy} @@ -27,7 +25,7 @@ def _proxies_dict(proxy): def make_xml_rpc_api_call(uri, method, args=None, headers=None, http_headers=None, timeout=None, proxy=None): - """ Makes a SoftLayer API call against the XML-RPC endpoint + """Makes a SoftLayer API call against the XML-RPC endpoint. :param string uri: endpoint URL :param string method: method to call E.G.: 'getObject' @@ -41,9 +39,9 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, largs = list(args) largs.insert(0, {'headers': headers}) - payload = xmlrpc_client.dumps(tuple(largs), - methodname=method, - allow_none=True) + payload = utils.xmlrpc_client.dumps(tuple(largs), + methodname=method, + allow_none=True) session = requests.Session() req = requests.Request('POST', uri, data=payload, headers=http_headers).prepare() @@ -59,34 +57,34 @@ def make_xml_rpc_api_call(uri, method, args=None, headers=None, LOGGER.debug(response.headers) LOGGER.debug(response.content) response.raise_for_status() - result = xmlrpc_client.loads(response.content,)[0][0] + result = utils.xmlrpc_client.loads(response.content,)[0][0] return result - except xmlrpc_client.Fault as ex: + except utils.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': NotWellFormed, - '-32701': UnsupportedEncoding, - '-32702': InvalidCharacter, - '-32600': SpecViolation, - '-32601': MethodNotFound, - '-32602': InvalidMethodParameters, - '-32603': InternalError, - '-32500': ApplicationError, - '-32400': RemoteSystemError, - '-32300': TransportError, + '-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, } - raise error_mapping.get(ex.faultCode, SoftLayerAPIError)( + raise error_mapping.get(ex.faultCode, exceptions.SoftLayerAPIError)( ex.faultCode, ex.faultString) except requests.HTTPError as ex: - raise TransportError(ex.response.status_code, str(ex)) + raise exceptions.TransportError(ex.response.status_code, str(ex)) except requests.RequestException as ex: - raise TransportError(0, str(ex)) + raise exceptions.TransportError(0, str(ex)) def make_rest_api_call(method, url, http_headers=None, timeout=None, proxy=None): - """ Makes a SoftLayer API call against the REST endpoint + """Makes a SoftLayer API call against the REST endpoint. :param string method: HTTP method: GET, POST, PUT, DELETE :param string url: endpoint URL @@ -108,8 +106,10 @@ def make_rest_api_call(method, url, except requests.HTTPError as ex: if url.endswith('.json'): content = json.loads(ex.response.content) - raise SoftLayerAPIError(ex.response.status_code, content['error']) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + content['error']) else: - raise SoftLayerAPIError(ex.response.status_code, ex.response.text) + raise exceptions.SoftLayerAPIError(ex.response.status_code, + ex.response.text) except requests.RequestException as ex: - raise TransportError(0, str(ex)) + raise exceptions.TransportError(0, str(ex)) diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py index 126d9dcd8..e0d28239d 100644 --- a/SoftLayer/utils.py +++ b/SoftLayer/utils.py @@ -6,6 +6,7 @@ :license: MIT, see LICENSE for more details. """ import re + import six UUID_RE = re.compile(r'^[0-9a-f\-]{36}$', re.I) @@ -19,9 +20,10 @@ 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. + """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. :: @@ -38,8 +40,11 @@ def lookup(dic, key, *keys): class NestedDict(dict): - """ This helps with accessing a heavily nested dictionary. Access to keys - which don't exist will result in a new, empty dictionary + """This helps with accessing a heavily nested dictionary. + + Dictionary where accessing keys that don't exist will return another + NestedDict object. + """ def __getitem__(self, key): @@ -48,9 +53,9 @@ def __getitem__(self, key): return self.setdefault(key, NestedDict()) def to_dict(self): - """ - Converts a NestedDict instance into a real dictionary. This is - needed for places where strict type checking is done. + """Converts a NestedDict instance into a real dictionary. + + This is needed for places where strict type checking is done. """ new_dict = {} for key, val in self.items(): @@ -62,8 +67,9 @@ def to_dict(self): def query_filter(query): - """ Translate a query-style string to a 'filter'. Query can be the - following formats: + """Translate a query-style string to a 'filter'. + + Query can be the following formats: Case Insensitive 'value' OR '*= value' Contains @@ -106,16 +112,20 @@ def query_filter(query): class IdentifierMixin(object): - """ This mixin provides an interface to provide multiple methods for - converting an 'indentifier' to an id """ + """Mixin used to resolve ids from other names of objects. + + This mixin provides an interface to provide multiple methods for + converting an 'indentifier' to an id + + """ resolvers = [] def resolve_ids(self, identifier): - """ Takes a string and tries to resolve to a list of matching ids. What - exactly 'identifier' can be depends on the resolvers + """Takes a string and tries to resolve to a list of matching ids. - :param string identifier: identifying string + What exactly 'identifier' can be depends on the resolvers + :param string identifier: identifying string :returns list: """ @@ -123,7 +133,7 @@ def resolve_ids(self, identifier): def resolve_ids(identifier, resolvers): - """ Resolves IDs given a list of functions + """Resolves IDs given a list of functions. :param string identifier: identifier string :param list resolvers: a list of functions diff --git a/setup.py b/setup.py index e49ee3b65..ad9be28a3 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ 'SoftLayer.CLI', 'SoftLayer.CLI.modules', 'SoftLayer.managers', + 'SoftLayer.testing', ], license='MIT', zip_safe=False, diff --git a/tox.ini b/tox.ini index 54378c7b5..8e68e57e4 100644 --- a/tox.ini +++ b/tox.ini @@ -18,13 +18,14 @@ commands = {envpython} setup.py nosetests [] [testenv:pep8] basepython = python2.7 deps = - flake8 - pep8-naming + hacking pylint commands = - flake8 --max-complexity=36 --statistics SoftLayer + flake8 --max-complexity=36 --statistics \ + --ignore=H401,H402,H404,H405 \ + SoftLayer pylint SoftLayer \ - --ignore=tests \ + --ignore=tests,testing \ -d R0903 \ # Too few public methods -d R0914 \ # Too many local variables -d R0201 \ # Method could be a function @@ -35,4 +36,11 @@ commands = --max-statements=86 \ --max-module-lines=1200 \ --max-returns=8 \ - --min-similarity-lines=50 # TODO: Remove + --min-similarity-lines=50 + pylint SoftLayer/testing \ + -d C0103 \ # Fixtures don't follow proper naming conventions + -d C0111 \ # Fixtures don't have docstrings + -d I0011 \ # Locally Disabling + --max-module-lines=2000 \ + --min-similarity-lines=50 \ + -r n From 3e8bc26d0f19fd9011109c7bf41399ec05689907 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 21:34:16 -0500 Subject: [PATCH 56/58] Adds ability to create multiple virtual servers in one call/order --- SoftLayer/managers/vs.py | 15 +++++++++++---- SoftLayer/testing/fixtures/Virtual_Guest.py | 1 + SoftLayer/tests/managers/vs_tests.py | 13 +++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py index 71b312d27..708376818 100644 --- a/SoftLayer/managers/vs.py +++ b/SoftLayer/managers/vs.py @@ -445,17 +445,24 @@ def create_instance(self, **kwargs): this VS placed. :param int private_vlan: The ID of the public VLAN on which you want this VS placed. - :param bool bare_metal: Flag to indicate if this is a bare metal server - or a dedicated server (default). :param list disks: A list of disk capacities for this server. :param string post_uri: The URI of the post-install script to run after reload :param bool private: If true, the VS will be provisioned only with access to the private network. Defaults to false :param list ssh_keys: The SSH keys to add to the root user + :param int nic_speed: The port speed to set """ - create_options = self._generate_create_dict(**kwargs) - return self.guest.createObject(create_options) + return self.guest.createObject(self._generate_create_dict(**kwargs)) + + def create_instances(self, config_list): + """ Creates multiple virtual server instances + + This takes a list of dictionaries using the same arguments as + create_instance(). + """ + return self.guest.createObjects([self._generate_create_dict(**kwargs) + for kwargs in config_list]) def change_port_speed(self, instance_id, public, speed): """ Allows you to change the port speed of a virtual server's NICs. diff --git a/SoftLayer/testing/fixtures/Virtual_Guest.py b/SoftLayer/testing/fixtures/Virtual_Guest.py index c1c6d2d7e..4323882d5 100644 --- a/SoftLayer/testing/fixtures/Virtual_Guest.py +++ b/SoftLayer/testing/fixtures/Virtual_Guest.py @@ -218,6 +218,7 @@ setPrivateNetworkInterfaceSpeed = True setPublicNetworkInterfaceSpeed = True createObject = getObject +createObjects = True generateOrderTemplate = {} setUserMetadata = ['meta'] reloadOperatingSystem = 'OK' diff --git a/SoftLayer/tests/managers/vs_tests.py b/SoftLayer/tests/managers/vs_tests.py index 8b8337ea4..46576d69a 100644 --- a/SoftLayer/tests/managers/vs_tests.py +++ b/SoftLayer/tests/managers/vs_tests.py @@ -142,6 +142,19 @@ def test_create_instance(self, create_dict): self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) + def test_create_instances(self): + self.vs.create_instances([{'cpus': 1, + 'memory': 1024, + 'hostname': 'server', + 'domain': 'example.com'}]) + self.client['Virtual_Guest'].createObjects.assert_called_once_with([ + {'domain': 'example.com', + 'hourlyBillingFlag': True, + 'localDiskFlag': True, + 'maxMemory': 1024, 'hostname': + 'server', + 'startCpus': 1}]) + def test_generate_os_and_image(self): self.assertRaises( ValueError, From 5d76462324bd914353ea4cd703113b5a727ee370 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 23:46:20 -0500 Subject: [PATCH 57/58] Version bump to v3.2.0 --- CHANGELOG | 12 ++++++++++++ SoftLayer/consts.py | 2 +- docs/conf.py | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8b2c3e9bb..4e352b227 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +3.2.0 + + * CLI+API: Added firewall manager and CLI module + + * CLI+API: Added iscsi manager and CLI module + + * API: Added ability to create multiple virtual servers at once to VSManager + + * API: Added OrderingManager. Remove hard-coded price IDs + + * Fixed several small bug fixes + 3.1.0 * CLI+API: Added CDN manager and CLI module diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 4590baa93..6ee3ddc47 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -5,7 +5,7 @@ :license: MIT, see LICENSE for more details. """ -VERSION = 'v3.1.0' +VERSION = 'v3.2.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/docs/conf.py b/docs/conf.py index 14b536f61..31889fed7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,9 +51,9 @@ # built documents. # # The short X.Y version. -version = '3.1.0' +version = '3.2.0' # The full version, including alpha/beta/rc tags. -release = '3.1.0' +release = '3.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 51cba64dcb28e3b1d137be56fef52ff0e67000be Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Tue, 8 Jul 2014 23:59:39 -0500 Subject: [PATCH 58/58] Version bump to v3.2.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ad9be28a3..c0c434b0e 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ setup( name='SoftLayer', - version='3.1.0', + version='3.2.0', description=description, long_description=long_description, author='SoftLayer Technologies, Inc.',