From cae5d4f9b3377b73a8805e94a62b130c0c4f042a Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 26 Sep 2013 18:02:56 -0500 Subject: [PATCH 01/14] SSHKeys are no longer a special-case for CCI creation --- SoftLayer/managers/cci.py | 13 ++----------- SoftLayer/tests/managers/cci_tests.py | 19 +------------------ 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 8e33b2435..784cc7bf1 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -331,7 +331,7 @@ def _generate_create_dict( data['postInstallScriptUri'] = post_uri if ssh_keys: - data['ssh_keys'] = ssh_keys + data['sshKeys'] = [{'id': key_id} for key_id in ssh_keys] return data @@ -365,16 +365,7 @@ def create_instance(self, **kwargs): """ Orders a new instance. See :func:`_generate_create_dict` for a list of available options. """ create_options = self._generate_create_dict(**kwargs) - - # createObject doesn't support SSH keys yet, so if we want to add an - # SSH key, we need to do something a bit more awkward - if kwargs.get('ssh_keys'): - order = self.guest.generateOrderTemplate(create_options) - order['sshKeys'] = [{'sshKeyIds': kwargs.get('ssh_keys')}] - result = self.client['Product_Order'].placeOrder(order) - return result['orderDetails']['virtualGuests'][0] - else: - return self.guest.createObject(create_options) + return self.guest.createObject(create_options) def change_port_speed(self, id, public, speed): """ Allows you to change the port speed of a CCI's NICs. diff --git a/SoftLayer/tests/managers/cci_tests.py b/SoftLayer/tests/managers/cci_tests.py index 909f40729..654a4ca5b 100644 --- a/SoftLayer/tests/managers/cci_tests.py +++ b/SoftLayer/tests/managers/cci_tests.py @@ -163,23 +163,6 @@ def test_create_instance(self, create_dict): self.client['Virtual_Guest'].createObject.assert_called_once_with( {'test': 1, 'verify': 1}) - @patch('SoftLayer.managers.cci.CCIManager._generate_create_dict') - def test_create_instance_with_ssh_keys(self, create_dict): - create_dict.return_value = {'test': 1, 'verify': 1} - f = self.client['Virtual_Guest'].generateOrderTemplate - f.return_value = { - 'prices': [100, 200] - } - - self.cci.create_instance(test=1, verify=1, ssh_keys=[30, 40]) - - create_dict.assert_called_once_with(test=1, verify=1, - ssh_keys=[30, 40]) - f.assert_called_once_with({'test': 1, 'verify': 1}) - self.client['Product_Order'].placeOrder.assert_called_once_with( - {'prices': [100, 200], 'sshKeys': [{'sshKeyIds': [30, 40]}]} - ) - def test_generate_os_and_image(self): self.assertRaises( ValueError, @@ -464,7 +447,7 @@ def test_generate_sshkey(self): 'localDiskFlag': True, 'operatingSystemReferenceCode': "STRING", 'hourlyBillingFlag': True, - 'ssh_keys': [543], + 'sshKeys': [{'id': 543}], } self.assertEqual(data, assert_data) From 13c6a77cb0fff1695a3725b7ebb343df5930e54c Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Wed, 2 Oct 2013 08:56:51 -0500 Subject: [PATCH 02/14] Fixing dictionary bug when ordering a private subnet --- SoftLayer/CLI/modules/subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/CLI/modules/subnet.py b/SoftLayer/CLI/modules/subnet.py index 06c3ab318..ee68219ff 100644 --- a/SoftLayer/CLI/modules/subnet.py +++ b/SoftLayer/CLI/modules/subnet.py @@ -93,7 +93,7 @@ def execute(client, args): t.align['cost'] = 'r' total = 0.0 - for price in result['prices']: + for price in result['orderDetails']['prices']: total += float(price.get('recurringFee', 0.0)) rate = "%.2f" % float(price['recurringFee']) From a4408523f82a205c29e244a4a1ca1fe9e6f84395 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Wed, 2 Oct 2013 10:38:34 -0500 Subject: [PATCH 03/14] API: Removes un-needed integer conversions in SoftLayer.Client.call() --- SoftLayer/API.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index b3b2940ed..69dbd7abc 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -149,7 +149,7 @@ def call(self, service, method, *args, **kwargs): headers.update(self.auth.get_headers()) if objectid is not None: - headers[service + 'InitParameters'] = {'id': int(objectid)} + headers[service + 'InitParameters'] = {'id': objectid} if objectmask is not None: headers.update(self.__format_object_mask(objectmask, service)) @@ -159,8 +159,8 @@ def call(self, service, method, *args, **kwargs): if limit: headers['resultLimit'] = { - 'limit': int(limit), - 'offset': int(offset) + 'limit': limit, + 'offset': offset, } http_headers = { From 23c3602fb8c1f867372ef637781638c6099c9944 Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Wed, 2 Oct 2013 14:30:49 -0500 Subject: [PATCH 04/14] Adds SSH key support when reloading CCIs and servers. Also updates changelog with recent fixes. --- CHANGELOG | 8 +++++++- SoftLayer/CLI/modules/cci.py | 16 ++++++++++++---- SoftLayer/CLI/modules/server.py | 12 ++++++++++-- SoftLayer/managers/cci.py | 7 +++++-- SoftLayer/managers/hardware.py | 7 +++++-- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cfddce6bb..790c07eb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +3.0.1 + + * CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. + + * CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. + 3.0.0 * Many bug fixes and consistency improvements @@ -7,7 +13,7 @@ * CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot * CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: - + * list, create, cancel and assign global IPs * list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' diff --git a/SoftLayer/CLI/modules/cci.py b/SoftLayer/CLI/modules/cci.py index a2a9acf66..330bc2501 100755 --- a/SoftLayer/CLI/modules/cci.py +++ b/SoftLayer/CLI/modules/cci.py @@ -656,13 +656,15 @@ def execute(client, args): class ReloadCCI(CLIRunnable): """ -usage: sl cci reload [options] +usage: sl cci reload [--key=KEY...] [options] Reload the OS on a CCI based on its current configuration Optional: - -i, --postinstall=URI Post-install script to download - (Only HTTPS executes, HTTP leaves file in /root) + -i, --postinstall=URI Post-install script to download + (Only HTTPS executes, HTTP leaves file in /root) + -k, --key=KEY SSH keys to add to the root user. Can be specified + multiple times """ action = 'reload' @@ -672,8 +674,14 @@ class ReloadCCI(CLIRunnable): def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') + keys = [] + if args.get('--key'): + for key in args.get('--key'): + key_id = resolve_id(SshKeyManager(client).resolve_ids, key, + 'SshKey') + keys.append(key_id) if args['--really'] or no_going_back(cci_id): - cci.reload_instance(cci_id, args['--postinstall']) + cci.reload_instance(cci_id, args['--postinstall'], keys) else: CLIAbort('Aborted') diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 911d34f3e..b65453775 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -189,13 +189,15 @@ def execute(client, args): class ServerReload(CLIRunnable): """ -usage: sl server reload [options] +usage: sl server reload [--key=KEY...] [options] Reload the OS on a hardware server based on its current configuration Optional: -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) + -k, --key=KEY SSH keys to add to the root user. Can be specified + multiple times """ action = 'reload' @@ -206,8 +208,14 @@ def execute(client, args): hardware = HardwareManager(client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') + keys = [] + if args.get('--key'): + for key in args.get('--key'): + key_id = resolve_id(SshKeyManager(client).resolve_ids, key, + 'SshKey') + keys.append(key_id) if args['--really'] or no_going_back(hardware_id): - hardware.reload(hardware_id, args['--postinstall']) + hardware.reload(hardware_id, args['--postinstall'], keys) else: CLIAbort('Aborted') diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 784cc7bf1..5ee36b7a1 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -197,13 +197,13 @@ def cancel_instance(self, id): """ return self.guest.deleteObject(id=id) - def reload_instance(self, id, post_uri=None): + def reload_instance(self, id, post_uri=None, ssh_keys=None): """ Perform an OS reload of an instance with its current configuration. :param integer id: the instance ID to reload :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 """ payload = { 'token': 'FORCE', @@ -213,6 +213,9 @@ def reload_instance(self, id, post_uri=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri + if ssh_keys: + payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] + return self.guest.reloadOperatingSystem('FORCE', payload['config'], id=id) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0b3a5a4df..68f944e97 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -269,13 +269,13 @@ def get_hardware(self, id, **kwargs): return self.hardware.getObject(id=id, **kwargs) - def reload(self, id, post_uri=None): + def reload(self, id, post_uri=None, ssh_keys=None): """ Perform an OS reload of a server with its current configuration. :param integer id: the instance ID to reload :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 """ payload = { @@ -286,6 +286,9 @@ def reload(self, id, post_uri=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri + if ssh_keys: + payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] + return self.hardware.reloadOperatingSystem('FORCE', payload['config'], id=id) From 287899f315ea0066c2141a75ba4df941c7b331f8 Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Wed, 2 Oct 2013 14:31:25 -0500 Subject: [PATCH 05/14] Revert "Adds SSH key support when reloading CCIs and servers. Also updates changelog with recent fixes." This reverts commit 23c3602fb8c1f867372ef637781638c6099c9944. --- CHANGELOG | 8 +------- SoftLayer/CLI/modules/cci.py | 16 ++++------------ SoftLayer/CLI/modules/server.py | 12 ++---------- SoftLayer/managers/cci.py | 7 ++----- SoftLayer/managers/hardware.py | 7 ++----- 5 files changed, 11 insertions(+), 39 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 790c07eb4..cfddce6bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,3 @@ -3.0.1 - - * CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. - - * CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. - 3.0.0 * Many bug fixes and consistency improvements @@ -13,7 +7,7 @@ * CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot * CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: - + * list, create, cancel and assign global IPs * list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' diff --git a/SoftLayer/CLI/modules/cci.py b/SoftLayer/CLI/modules/cci.py index 330bc2501..a2a9acf66 100755 --- a/SoftLayer/CLI/modules/cci.py +++ b/SoftLayer/CLI/modules/cci.py @@ -656,15 +656,13 @@ def execute(client, args): class ReloadCCI(CLIRunnable): """ -usage: sl cci reload [--key=KEY...] [options] +usage: sl cci reload [options] Reload the OS on a CCI based on its current configuration Optional: - -i, --postinstall=URI Post-install script to download - (Only HTTPS executes, HTTP leaves file in /root) - -k, --key=KEY SSH keys to add to the root user. Can be specified - multiple times + -i, --postinstall=URI Post-install script to download + (Only HTTPS executes, HTTP leaves file in /root) """ action = 'reload' @@ -674,14 +672,8 @@ class ReloadCCI(CLIRunnable): def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') - keys = [] - if args.get('--key'): - for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(client).resolve_ids, key, - 'SshKey') - keys.append(key_id) if args['--really'] or no_going_back(cci_id): - cci.reload_instance(cci_id, args['--postinstall'], keys) + cci.reload_instance(cci_id, args['--postinstall']) else: CLIAbort('Aborted') diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index b65453775..911d34f3e 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -189,15 +189,13 @@ def execute(client, args): class ServerReload(CLIRunnable): """ -usage: sl server reload [--key=KEY...] [options] +usage: sl server reload [options] Reload the OS on a hardware server based on its current configuration Optional: -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) - -k, --key=KEY SSH keys to add to the root user. Can be specified - multiple times """ action = 'reload' @@ -208,14 +206,8 @@ def execute(client, args): hardware = HardwareManager(client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') - keys = [] - if args.get('--key'): - for key in args.get('--key'): - key_id = resolve_id(SshKeyManager(client).resolve_ids, key, - 'SshKey') - keys.append(key_id) if args['--really'] or no_going_back(hardware_id): - hardware.reload(hardware_id, args['--postinstall'], keys) + hardware.reload(hardware_id, args['--postinstall']) else: CLIAbort('Aborted') diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 5ee36b7a1..784cc7bf1 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -197,13 +197,13 @@ def cancel_instance(self, id): """ return self.guest.deleteObject(id=id) - def reload_instance(self, id, post_uri=None, ssh_keys=None): + def reload_instance(self, id, post_uri=None): """ Perform an OS reload of an instance with its current configuration. :param integer id: the instance ID to reload :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 + """ payload = { 'token': 'FORCE', @@ -213,9 +213,6 @@ def reload_instance(self, id, post_uri=None, ssh_keys=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri - if ssh_keys: - payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] - return self.guest.reloadOperatingSystem('FORCE', payload['config'], id=id) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 68f944e97..0b3a5a4df 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -269,13 +269,13 @@ def get_hardware(self, id, **kwargs): return self.hardware.getObject(id=id, **kwargs) - def reload(self, id, post_uri=None, ssh_keys=None): + def reload(self, id, post_uri=None): """ Perform an OS reload of a server with its current configuration. :param integer id: the instance ID to reload :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 + """ payload = { @@ -286,9 +286,6 @@ def reload(self, id, post_uri=None, ssh_keys=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri - if ssh_keys: - payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] - return self.hardware.reloadOperatingSystem('FORCE', payload['config'], id=id) From ff4b9dcae716ccbbc42436dad12bfaa0182b416b Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Wed, 2 Oct 2013 14:33:06 -0500 Subject: [PATCH 06/14] Adds support for specifying SSH keys when reloading CCIs and servers. Also updates the changelog with info about recent fixes. --- CHANGELOG | 8 +++++++- SoftLayer/CLI/modules/cci.py | 16 ++++++++++++---- SoftLayer/CLI/modules/server.py | 12 ++++++++++-- SoftLayer/managers/cci.py | 7 +++++-- SoftLayer/managers/hardware.py | 7 +++++-- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cfddce6bb..790c07eb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ +3.0.1 + + * CLI: Fixed an error message about pricing information that appeared when ordering a new private subnet. + + * CLI+API: Added ability to specify SSH keys when reloading CCIs and servers. + 3.0.0 * Many bug fixes and consistency improvements @@ -7,7 +13,7 @@ * CLI+API: Improved dedicated server ordering. Adds power management for hardware servers: power-on, power-off, power-cycle, reboot * CLI+API: Adds a networking manager and adds several network-related CLI modules. This includes the ability to: - + * list, create, cancel and assign global IPs * list, create, cancel and detail subnets. Also has the ability to lookup details about an IP address with 'sl subnet lookup' diff --git a/SoftLayer/CLI/modules/cci.py b/SoftLayer/CLI/modules/cci.py index a2a9acf66..330bc2501 100755 --- a/SoftLayer/CLI/modules/cci.py +++ b/SoftLayer/CLI/modules/cci.py @@ -656,13 +656,15 @@ def execute(client, args): class ReloadCCI(CLIRunnable): """ -usage: sl cci reload [options] +usage: sl cci reload [--key=KEY...] [options] Reload the OS on a CCI based on its current configuration Optional: - -i, --postinstall=URI Post-install script to download - (Only HTTPS executes, HTTP leaves file in /root) + -i, --postinstall=URI Post-install script to download + (Only HTTPS executes, HTTP leaves file in /root) + -k, --key=KEY SSH keys to add to the root user. Can be specified + multiple times """ action = 'reload' @@ -672,8 +674,14 @@ class ReloadCCI(CLIRunnable): def execute(client, args): cci = CCIManager(client) cci_id = resolve_id(cci.resolve_ids, args.get(''), 'CCI') + keys = [] + if args.get('--key'): + for key in args.get('--key'): + key_id = resolve_id(SshKeyManager(client).resolve_ids, key, + 'SshKey') + keys.append(key_id) if args['--really'] or no_going_back(cci_id): - cci.reload_instance(cci_id, args['--postinstall']) + cci.reload_instance(cci_id, args['--postinstall'], keys) else: CLIAbort('Aborted') diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index 911d34f3e..b65453775 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -189,13 +189,15 @@ def execute(client, args): class ServerReload(CLIRunnable): """ -usage: sl server reload [options] +usage: sl server reload [--key=KEY...] [options] Reload the OS on a hardware server based on its current configuration Optional: -i, --postinstall=URI Post-install script to download (Only HTTPS executes, HTTP leaves file in /root) + -k, --key=KEY SSH keys to add to the root user. Can be specified + multiple times """ action = 'reload' @@ -206,8 +208,14 @@ def execute(client, args): hardware = HardwareManager(client) hardware_id = resolve_id( hardware.resolve_ids, args.get(''), 'hardware') + keys = [] + if args.get('--key'): + for key in args.get('--key'): + key_id = resolve_id(SshKeyManager(client).resolve_ids, key, + 'SshKey') + keys.append(key_id) if args['--really'] or no_going_back(hardware_id): - hardware.reload(hardware_id, args['--postinstall']) + hardware.reload(hardware_id, args['--postinstall'], keys) else: CLIAbort('Aborted') diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 784cc7bf1..5ee36b7a1 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -197,13 +197,13 @@ def cancel_instance(self, id): """ return self.guest.deleteObject(id=id) - def reload_instance(self, id, post_uri=None): + def reload_instance(self, id, post_uri=None, ssh_keys=None): """ Perform an OS reload of an instance with its current configuration. :param integer id: the instance ID to reload :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 """ payload = { 'token': 'FORCE', @@ -213,6 +213,9 @@ def reload_instance(self, id, post_uri=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri + if ssh_keys: + payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] + return self.guest.reloadOperatingSystem('FORCE', payload['config'], id=id) diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 0b3a5a4df..68f944e97 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -269,13 +269,13 @@ def get_hardware(self, id, **kwargs): return self.hardware.getObject(id=id, **kwargs) - def reload(self, id, post_uri=None): + def reload(self, id, post_uri=None, ssh_keys=None): """ Perform an OS reload of a server with its current configuration. :param integer id: the instance ID to reload :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 """ payload = { @@ -286,6 +286,9 @@ def reload(self, id, post_uri=None): if post_uri: payload['config']['customProvisionScriptUri'] = post_uri + if ssh_keys: + payload['config']['sshKeyIds'] = [key_id for key_id in ssh_keys] + return self.hardware.reloadOperatingSystem('FORCE', payload['config'], id=id) From 003b674d6322f04bca3d4fe0ee663ad371982b14 Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Wed, 2 Oct 2013 14:46:18 -0500 Subject: [PATCH 07/14] Fixing unit tests for the new SSH key on reload functionality --- SoftLayer/tests/CLI/modules/server_tests.py | 4 ++-- SoftLayer/tests/managers/cci_tests.py | 5 +++-- SoftLayer/tests/managers/hardware_tests.py | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 17f62fbef..14820f8e8 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -197,10 +197,10 @@ def test_ServerReload( ngb_mock.return_value = False # Check the positive case - args = {'--really': True, '--postinstall': None} + args = {'--really': True, '--postinstall': None, '--key': [12345]} server.ServerReload.execute(self.client, args) - reload_mock.assert_called_with(hw_id, args['--postinstall']) + reload_mock.assert_called_with(hw_id, args['--postinstall'], [12345]) # Now check to make sure we properly call CLIAbort in the negative case args['--really'] = False diff --git a/SoftLayer/tests/managers/cci_tests.py b/SoftLayer/tests/managers/cci_tests.py index 654a4ca5b..02cf03c74 100644 --- a/SoftLayer/tests/managers/cci_tests.py +++ b/SoftLayer/tests/managers/cci_tests.py @@ -141,11 +141,12 @@ def test_cancel_instance(self): def test_reload_instance(self): post_uri = 'http://test.sftlyr.ws/test.sh' - self.cci.reload_instance(id=1, post_uri=post_uri) + self.cci.reload_instance(id=1, post_uri=post_uri, ssh_keys=[1701]) service = self.client['Virtual_Guest'] f = service.reloadOperatingSystem f.assert_called_once_with('FORCE', - {'customProvisionScriptUri': post_uri}, id=1) + {'customProvisionScriptUri': post_uri, + 'sshKeyIds': [1701]}, id=1) @patch('SoftLayer.managers.cci.CCIManager._generate_create_dict') def test_create_verify(self, create_dict): diff --git a/SoftLayer/tests/managers/hardware_tests.py b/SoftLayer/tests/managers/hardware_tests.py index 9691054ad..e5680782a 100644 --- a/SoftLayer/tests/managers/hardware_tests.py +++ b/SoftLayer/tests/managers/hardware_tests.py @@ -95,10 +95,11 @@ def test_get_hardware(self): def test_reload(self): post_uri = 'http://test.sftlyr.ws/test.sh' - self.hardware.reload(id=1, post_uri=post_uri) + self.hardware.reload(id=1, post_uri=post_uri, ssh_keys=[1701]) f = self.client.__getitem__().reloadOperatingSystem f.assert_called_once_with('FORCE', - {'customProvisionScriptUri': post_uri}, id=1) + {'customProvisionScriptUri': post_uri, + 'sshKeyIds': [1701]}, id=1) def test_get_bare_metal_create_options_returns_none_on_error(self): self.client['Product_Package'].getAllObjects.return_value = [ From 01e4b026de2b0345d173b9efc6823be724902adb Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Thu, 3 Oct 2013 13:40:31 -0500 Subject: [PATCH 08/14] Adds Coverage Enforcement for Tests --- setup.cfg | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f443a1601..eb9d326aa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,5 +2,7 @@ verbosity=2 detailed-errors=1 with-coverage=1 +cover-min-percentage = 100 +cover-erase = true cover-package=SoftLayer -cover-html=1 \ No newline at end of file +cover-html=1 From 948ecdc1341dc4bc5d60f2b7007be46f49feea7e Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Sat, 5 Oct 2013 18:13:08 -0500 Subject: [PATCH 09/14] Converts several try/except to the newer style --- SoftLayer/CLI/core.py | 2 +- SoftLayer/CLI/modules/config.py | 4 ++-- SoftLayer/managers/cci.py | 2 +- SoftLayer/managers/metadata.py | 2 +- SoftLayer/tests/api_tests.py | 3 +-- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py index 1a1428f57..1ab546509 100644 --- a/SoftLayer/CLI/core.py +++ b/SoftLayer/CLI/core.py @@ -198,7 +198,7 @@ def main(args=sys.argv[1:], env=Environment()): env.err('') env.err(str(e)) exit_status = 1 - except InvalidModule, e: + except InvalidModule as e: env.err(resolver.get_main_help()) if e.module_name: env.err('') diff --git a/SoftLayer/CLI/modules/config.py b/SoftLayer/CLI/modules/config.py index b06649df4..e5ade3cd3 100644 --- a/SoftLayer/CLI/modules/config.py +++ b/SoftLayer/CLI/modules/config.py @@ -156,8 +156,8 @@ def execute(cls, client, args): config.set('softlayer', 'api_key', settings['api_key']) config.set('softlayer', 'endpoint_url', settings['endpoint_url']) - f = os.fdopen( - os.open(config_path, os.O_WRONLY | os.O_CREAT, 0600), 'w') + f = os.fdopen(os.open( + config_path, (os.O_WRONLY | os.O_CREAT), 0600), 'w') try: config.write(f) finally: diff --git a/SoftLayer/managers/cci.py b/SoftLayer/managers/cci.py index 5ee36b7a1..b231be7db 100644 --- a/SoftLayer/managers/cci.py +++ b/SoftLayer/managers/cci.py @@ -309,7 +309,7 @@ def _generate_create_dict( "networkVlan": {"id": int(private_vlan)}}}) if userdata: - data['userData'] = [{'value': userdata}, ] + data['userData'] = [{'value': userdata}] if nic_speed: data['networkComponents'] = [{'maxSpeed': nic_speed}] diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py index 00419c795..a129dd6b9 100644 --- a/SoftLayer/managers/metadata.py +++ b/SoftLayer/managers/metadata.py @@ -64,7 +64,7 @@ def make_request(self, path): return make_rest_api_call('GET', url, http_headers={'User-Agent': USER_AGENT}, timeout=self.timeout) - except SoftLayerAPIError, e: + except SoftLayerAPIError as e: if e.faultCode == 404: return None raise e diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index 4919184f0..2c900e708 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -6,7 +6,6 @@ :license: MIT, see LICENSE for more details. """ from mock import patch, call, Mock, MagicMock -import datetime import SoftLayer import SoftLayer.API @@ -151,7 +150,7 @@ def test_mask_call_v2_dot(self, make_xml_rpc_api_call): def test_mask_call_invalid_mask(self): try: self.client['SERVICE'].METHOD(mask="mask[something.nested") - except SoftLayer.SoftLayerError, e: + except SoftLayer.SoftLayerError as e: self.assertIn('Malformed Mask', str(e)) else: self.fail('No exception raised') From f610fa2c0d4bd2912bd550b880bffc0a3821ca72 Mon Sep 17 00:00:00 2001 From: Kevin McDonald Date: Mon, 7 Oct 2013 15:16:01 -0500 Subject: [PATCH 10/14] Simplifies how masks are re-formatted and allows for more advanced masks --- SoftLayer/API.py | 13 +++---------- SoftLayer/tests/api_tests.py | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/SoftLayer/API.py b/SoftLayer/API.py index 69dbd7abc..5f2b4175a 100644 --- a/SoftLayer/API.py +++ b/SoftLayer/API.py @@ -10,7 +10,6 @@ from consts import API_PUBLIC_ENDPOINT, API_PRIVATE_ENDPOINT, USER_AGENT from transports import make_xml_rpc_api_call -from exceptions import SoftLayerError from auth import TokenAuthentication from config import get_client_settings @@ -242,15 +241,9 @@ def __format_object_mask(self, objectmask, service): mheader = self._prefix + 'ObjectMask' objectmask = objectmask.strip() - if objectmask.startswith('mask'): - objectmask = objectmask[4:] - if objectmask[0] == '.': - objectmask = objectmask[1:] - elif objectmask[0] == '[' and objectmask[-1] == ']': - objectmask = objectmask[1:-1] - else: - raise SoftLayerError('Malformed Mask: %s' % objectmask) - objectmask = "mask[%s]" % objectmask + if not objectmask.startswith('mask') \ + and not objectmask.startswith('['): + objectmask = "mask[%s]" % objectmask return {mheader: {'mask': objectmask}} diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py index 2c900e708..06c84c5c5 100644 --- a/SoftLayer/tests/api_tests.py +++ b/SoftLayer/tests/api_tests.py @@ -140,20 +140,27 @@ def test_mask_call_v2_dot(self, make_xml_rpc_api_call): headers={ 'authenticate': { 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, - 'SoftLayer_ObjectMask': {'mask': 'mask[something.nested]'}}, + 'SoftLayer_ObjectMask': {'mask': 'mask.something.nested'}}, timeout=None, http_headers={ 'Content-Type': 'application/xml', 'User-Agent': USER_AGENT, }) - def test_mask_call_invalid_mask(self): - try: - self.client['SERVICE'].METHOD(mask="mask[something.nested") - except SoftLayer.SoftLayerError as e: - self.assertIn('Malformed Mask', str(e)) - else: - self.fail('No exception raised') + @patch('SoftLayer.API.make_xml_rpc_api_call') + def test_mask_call_no_mask_prefix(self, make_xml_rpc_api_call): + self.client['SERVICE'].METHOD(mask="something.nested") + make_xml_rpc_api_call.assert_called_with( + 'ENDPOINT/SoftLayer_SERVICE', 'METHOD', (), + headers={ + 'authenticate': { + 'username': 'doesnotexist', 'apiKey': 'issurelywrong'}, + 'SoftLayer_ObjectMask': {'mask': 'mask[something.nested]'}}, + timeout=None, + http_headers={ + 'Content-Type': 'application/xml', + 'User-Agent': USER_AGENT, + }) @patch('SoftLayer.API.Client.iter_call') def test_iterate(self, _iter_call): From db4be82402a89c087c212898a91e3eb0d2f6c9cf Mon Sep 17 00:00:00 2001 From: Brian Cline Date: Mon, 7 Oct 2013 17:10:06 -0500 Subject: [PATCH 11/14] Refines and extends active transaction info (see notes) - Non-TTY output no longer provides a transaction's friendlyName (i.e, "Power on server"), which may have spaces, instead resorting to the transaction name (i.e., "POWER_ON"). Normal TTY output retains the friendlyName. - Adds a formatting helper for objects that may have an activeTransaction - Existing CCI list uses new helper - Hardware lists now include the active transaction, if any - Hardware details and CCI detail output now includes the active transaction, if any --- SoftLayer/CLI/formatting.py | 15 +++++++++++++++ SoftLayer/CLI/helpers.py | 5 +++-- SoftLayer/CLI/modules/cci.py | 7 ++++--- SoftLayer/CLI/modules/server.py | 8 +++++--- SoftLayer/managers/hardware.py | 13 ++++++++++--- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/SoftLayer/CLI/formatting.py b/SoftLayer/CLI/formatting.py index 53dc6cf9b..69f008496 100644 --- a/SoftLayer/CLI/formatting.py +++ b/SoftLayer/CLI/formatting.py @@ -126,6 +126,21 @@ def listing(items, separator=','): return SequentialOutput(separator, items) +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. + + :param item: An object capable of having an active transaction + """ + if not item['activeTransaction']['transactionStatus']: + return blank() + + return FormattedItem( + item['activeTransaction']['transactionStatus'].get('name'), + item['activeTransaction']['transactionStatus'].get('friendlyName')) + + def valid_response(prompt, *valid): ans = raw_input(prompt).lower() diff --git a/SoftLayer/CLI/helpers.py b/SoftLayer/CLI/helpers.py index c6e05e7f1..20bb1dae5 100644 --- a/SoftLayer/CLI/helpers.py +++ b/SoftLayer/CLI/helpers.py @@ -12,7 +12,8 @@ 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, valid_response) + no_going_back, mb_to_gb, gb, listing, blank, format_output, + active_txn, valid_response) from template import update_with_template_args, export_to_template __all__ = [ @@ -23,7 +24,7 @@ # Formatting 'Table', 'KeyValueTable', 'FormattedItem', 'SequentialOutput', 'valid_response', 'confirm', 'no_going_back', 'mb_to_gb', 'gb', - 'listing', 'format_output', 'blank', + 'listing', 'format_output', 'blank', 'active_txn', # Template 'update_with_template_args', 'export_to_template', ] diff --git a/SoftLayer/CLI/modules/cci.py b/SoftLayer/CLI/modules/cci.py index 330bc2501..3af2c684e 100755 --- a/SoftLayer/CLI/modules/cci.py +++ b/SoftLayer/CLI/modules/cci.py @@ -37,7 +37,8 @@ FormattedItem) from SoftLayer.CLI.helpers import ( CLIAbort, ArgumentError, NestedDict, blank, resolve_id, KeyValueTable, - update_with_template_args, FALSE_VALUES, export_to_template) + update_with_template_args, FALSE_VALUES, export_to_template, + active_txn) class ListCCIs(CLIRunnable): @@ -109,8 +110,7 @@ def execute(client, args): mb_to_gb(guest['maxMemory']), guest['primaryIpAddress'] or blank(), guest['primaryBackendIpAddress'] or blank(), - guest['activeTransaction']['transactionStatus'].get( - 'friendlyName') or blank(), + active_txn(guest), ]) return t @@ -145,6 +145,7 @@ def execute(client, args): result['status']['keyName'] or blank(), result['status']['name'] or blank() )]) + t.add_row(['active_transaction', active_txn(result)]) t.add_row(['state', FormattedItem( lookup(result, 'powerState', 'keyName'), lookup(result, 'powerState', 'name'), diff --git a/SoftLayer/CLI/modules/server.py b/SoftLayer/CLI/modules/server.py index b65453775..116fc7fe2 100644 --- a/SoftLayer/CLI/modules/server.py +++ b/SoftLayer/CLI/modules/server.py @@ -29,8 +29,8 @@ from os import linesep from SoftLayer.CLI.helpers import ( CLIRunnable, Table, KeyValueTable, FormattedItem, NestedDict, CLIAbort, - blank, listing, gb, no_going_back, resolve_id, confirm, ArgumentError, - update_with_template_args, export_to_template) + blank, listing, gb, active_txn, no_going_back, resolve_id, confirm, + ArgumentError, update_with_template_args, export_to_template) from SoftLayer import HardwareManager, SshKeyManager @@ -87,7 +87,8 @@ def execute(client, args): 'cores', 'memory', 'primary_ip', - 'backend_ip' + 'backend_ip', + 'active_transaction' ]) t.sortby = args.get('--sortby') or 'host' @@ -101,6 +102,7 @@ def execute(client, args): gb(server['memoryCapacity']), server['primaryIpAddress'] or blank(), server['primaryBackendIpAddress'] or blank(), + active_txn(server), ]) return t diff --git a/SoftLayer/managers/hardware.py b/SoftLayer/managers/hardware.py index 68f944e97..ccf2af125 100644 --- a/SoftLayer/managers/hardware.py +++ b/SoftLayer/managers/hardware.py @@ -99,7 +99,7 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, """ if 'mask' not in kwargs: - items = set([ + hw_items = set([ 'id', 'hostname', 'domain', @@ -112,7 +112,14 @@ def list_hardware(self, tags=None, cpus=None, memory=None, hostname=None, 'primaryIpAddress', 'datacenter', ]) - kwargs['mask'] = "mask[%s]" % ','.join(items) + server_items = set([ + 'activeTransaction[id, transactionStatus[friendlyName,name]]', + ]) + + kwargs['mask'] = '[mask[%s],' \ + ' mask(SoftLayer_Hardware_Server)[%s]]' % \ + (','.join(hw_items), + ','.join(server_items)) _filter = NestedDict(kwargs.get('filter') or {}) if tags: @@ -256,7 +263,7 @@ def get_hardware(self, id, **kwargs): 'networkComponents.primarySubnet[id, netmask,' 'broadcastAddress, networkIdentifier, gateway]', 'hardwareChassis[id,name]', - 'activeTransaction.id', + 'activeTransaction[id, transactionStatus[friendlyName,name]]', 'operatingSystem.softwareLicense.' 'softwareDescription[manufacturer,name,version,referenceCode]', 'operatingSystem.passwords[username,password]', From f0edc2a78994eff7ea9ae119c0b3bb041804c346 Mon Sep 17 00:00:00 2001 From: Brian Cline Date: Tue, 8 Oct 2013 21:58:20 -0500 Subject: [PATCH 12/14] Update server tests to yield 100% coverage on new transaction code. --- SoftLayer/tests/CLI/modules/server_tests.py | 6 ++++-- SoftLayer/tests/mocks/hardware_mock.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/SoftLayer/tests/CLI/modules/server_tests.py b/SoftLayer/tests/CLI/modules/server_tests.py index 14820f8e8..57cd00f8f 100644 --- a/SoftLayer/tests/CLI/modules/server_tests.py +++ b/SoftLayer/tests/CLI/modules/server_tests.py @@ -171,7 +171,8 @@ def test_ListServers(self): 'memory': 2048, 'cores': 2, 'id': 1000, - 'backend_ip': '10.1.0.2' + 'backend_ip': '10.1.0.2', + 'active_transaction': 'TXN_NAME' }, { 'datacenter': 'TEST00', @@ -180,7 +181,8 @@ def test_ListServers(self): 'memory': 4096, 'cores': 4, 'id': 1001, - 'backend_ip': '10.1.0.3' + 'backend_ip': '10.1.0.3', + 'active_transaction': None } ] diff --git a/SoftLayer/tests/mocks/hardware_mock.py b/SoftLayer/tests/mocks/hardware_mock.py index 6355cd252..e225c25fd 100644 --- a/SoftLayer/tests/mocks/hardware_mock.py +++ b/SoftLayer/tests/mocks/hardware_mock.py @@ -82,6 +82,13 @@ def get_raw_hardware_mocks(): 'tagReferences': [ {'tag': {'name': 'test_tag'}} ], + 'activeTransaction': { + 'transactionStatus': { + 'name': 'TXN_NAME', + 'friendlyName': 'Friendly Transaction Name', + 'id': 6660 + } + } }, 1001: { 'id': 1001, From 24d439cab75dbed42bda9623a51f0baf1e4ef85d Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 11 Oct 2013 08:43:41 -0500 Subject: [PATCH 13/14] Updating version number --- SoftLayer/consts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py index 5fac18271..f16bcc04c 100644 --- a/SoftLayer/consts.py +++ b/SoftLayer/consts.py @@ -6,7 +6,7 @@ :copyright: (c) 2013, SoftLayer Technologies, Inc. All rights reserved. :license: MIT, see LICENSE for more details. """ -VERSION = 'v3.0.0' +VERSION = 'v3.0.1' API_PUBLIC_ENDPOINT = 'https://api.softlayer.com/xmlrpc/v3/' API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3/' API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3/' From 284df5a380bd57ae8c5b6dfdd4aaa08e94398fab Mon Sep 17 00:00:00 2001 From: Nathan Beittenmiller Date: Fri, 11 Oct 2013 09:11:49 -0500 Subject: [PATCH 14/14] Additional version fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e2e4d00e9..51a2fbb3b 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ setup( name='SoftLayer', - version='3.0.0', + version='3.0.1', description=description, long_description=long_description, author='SoftLayer Technologies, Inc.',