Repository: libcloud Updated Branches: refs/heads/trunk 860674ac7 -> f007edaaf
Adds the following methods for the Softlayer driver: - list_key_pairs() - get_key_pair() - import_key_pair_from_string() - delete_key_pair() - create_key_pair() (only if Pycripto is installed) Adds tests for all new methods. Modifies the test_create_node() to use an ssh key. Adds a new property on the Softlayer node "ex_keyname"which is the name of the key to be associated to the new node. Closes #354 Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/f007edaa Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/f007edaa Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/f007edaa Branch: refs/heads/trunk Commit: f007edaafcae79afb2890e75cd2ba1f91aa26d7e Parents: 860674a Author: Itxaka Serrano <[email protected]> Authored: Sun Aug 31 18:07:14 2014 +0200 Committer: Tomaz Muraus <[email protected]> Committed: Sun Aug 31 18:34:45 2014 +0200 ---------------------------------------------------------------------- CHANGES.rst | 4 + libcloud/compute/drivers/softlayer.py | 96 +++++++++++++++++++- .../v3__SoftLayer_Account_getSshKeys.xml | 63 +++++++++++++ ..._SoftLayer_Security_Ssh_Key_createObject.xml | 39 ++++++++ ..._SoftLayer_Security_Ssh_Key_deleteObject.xml | 15 +++ ...v3__SoftLayer_Security_Ssh_Key_getObject.xml | 33 +++++++ libcloud/test/compute/test_softlayer.py | 64 ++++++++++++- 7 files changed, 308 insertions(+), 6 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 53345c8..69ab399 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -78,6 +78,10 @@ Compute (LIBCLOUD-544, GITHUB-349, GITHUB-353) [Raphael Theberge] +- Add SSH key pair management methods to the Softlayer driver. + (GITHUB-321, GITHUB-354) + [Itxaka Serrano] + Storage ~~~~~~~ http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/compute/drivers/softlayer.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/softlayer.py b/libcloud/compute/drivers/softlayer.py index 4fe8cdd..61a1e1a 100644 --- a/libcloud/compute/drivers/softlayer.py +++ b/libcloud/compute/drivers/softlayer.py @@ -17,13 +17,19 @@ Softlayer driver """ import time +try: + from Crypto.PublicKey import RSA + crypto = True +except ImportError: + crypto = False from libcloud.common.base import ConnectionUserAndKey from libcloud.common.xmlrpc import XMLRPCResponse, XMLRPCConnection from libcloud.common.types import InvalidCredsError, LibcloudError from libcloud.compute.types import Provider, NodeState from libcloud.compute.base import NodeDriver, Node, NodeLocation, NodeSize, \ - NodeImage + NodeImage, KeyPair +from libcloud.compute.types import KeyPairDoesNotExistError DEFAULT_DOMAIN = 'example.com' DEFAULT_CPU_SIZE = 1 @@ -158,7 +164,6 @@ class SoftLayerConnection(XMLRPCConnection, ConnectionUserAndKey): args = ({'headers': headers}, ) + args endpoint = '%s/%s' % (self.endpoint, service) - return super(SoftLayerConnection, self).request(method, *args, **{'endpoint': endpoint}) @@ -204,7 +209,7 @@ class SoftLayerNodeDriver(NodeDriver): website = 'http://www.softlayer.com/' type = Provider.SOFTLAYER - features = {'create_node': ['generates_password']} + features = {'create_node': ['generates_password', 'ssh_key']} def _to_node(self, host): try: @@ -330,6 +335,8 @@ class SoftLayerNodeDriver(NodeDriver): :type ex_datacenter: ``str`` :keyword ex_os: e.g. UBUNTU_LATEST :type ex_os: ``str`` + :keyword ex_keyname: The name of the key pair + :type ex_keyname: ``str`` """ name = kwargs['name'] os = 'DEBIAN_LATEST' @@ -402,6 +409,9 @@ class SoftLayerNodeDriver(NodeDriver): if datacenter: newCCI['datacenter'] = {'name': datacenter} + if 'ex_keyname' in kwargs: + newCCI['sshKeys'] = [self._key_name_to_id(kwargs['ex_keyname'])] + res = self.connection.request( 'SoftLayer_Virtual_Guest', 'createObject', newCCI ).object @@ -411,6 +421,59 @@ class SoftLayerNodeDriver(NodeDriver): return self._to_node(raw_node) + def list_key_pairs(self): + result = self.connection.request( + 'SoftLayer_Account', 'getSshKeys' + ).object + elems = [x for x in result] + key_pairs = self._to_key_pairs(elems=elems) + return key_pairs + + def get_key_pair(self, name): + key_id = self._key_name_to_id(name=name) + result = self.connection.request( + 'SoftLayer_Security_Ssh_Key', 'getObject', id=key_id + ).object + return self._to_key_pair(result) + + # TODO: Check this with the libcloud guys, + # can we create new dependencies? + def create_key_pair(self, name, ex_size=4096): + if crypto is False: + raise NotImplementedError('create_key_pair needs' + 'the pycrypto library') + key = RSA.generate(ex_size) + new_key = { + 'key': key.publickey().exportKey('OpenSSH'), + 'label': name, + 'notes': '', + } + result = self.connection.request( + 'SoftLayer_Security_Ssh_Key', 'createObject', new_key + ).object + result['private'] = key.exportKey('PEM') + return self._to_key_pair(result) + + def import_key_pair_from_string(self, name, key_material): + new_key = { + 'key': key_material, + 'label': name, + 'notes': '', + } + result = self.connection.request( + 'SoftLayer_Security_Ssh_Key', 'createObject', new_key + ).object + + key_pair = self._to_key_pair(result) + return key_pair + + def delete_key_pair(self, key_pair): + key = self._key_name_to_id(key_pair) + result = self.connection.request( + 'SoftLayer_Security_Ssh_Key', 'deleteObject', id=key + ).object + return result + def _to_image(self, img): return NodeImage( id=img['template']['operatingSystemReferenceCode'], @@ -467,8 +530,31 @@ class SoftLayerNodeDriver(NodeDriver): }, } res = self.connection.request( - "SoftLayer_Account", - "getVirtualGuests", + 'SoftLayer_Account', + 'getVirtualGuests', object_mask=mask ).object return [self._to_node(h) for h in res] + + def _to_key_pairs(self, elems): + key_pairs = [self._to_key_pair(elem=elem) for elem in elems] + return key_pairs + + def _to_key_pair(self, elem): + key_pair = KeyPair(name=elem['label'], + public_key=elem['key'], + fingerprint=elem['fingerprint'], + private_key=elem.get('private', None), + driver=self, + extra={'id': elem['id']}) + return key_pair + + def _key_name_to_id(self, name): + result = self.connection.request( + 'SoftLayer_Account', 'getSshKeys' + ).object + key_id = [x for x in result if x['label'] == name] + if len(key_id) == 0: + raise KeyPairDoesNotExistError(name, self) + else: + return int(key_id[0]['id']) http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Account_getSshKeys.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Account_getSshKeys.xml b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Account_getSshKeys.xml new file mode 100644 index 0000000..a06952c --- /dev/null +++ b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Account_getSshKeys.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<params> + <param> + <value> + <struct> + <member> + <name>id</name> + <value> + <int>1</int> + </value> + </member> + <member> + <name>key</name> + <value> + <string>ssh-key</string> + </value> + </member> + <member> + <name>label</name> + <value> + <string>test1</string> + </value> + </member> + <member> + <name>fingerprint</name> + <value> + <string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string> + </value> + </member> + </struct> + </value> + </param> + <param> + <value> + <struct> + <member> + <name>id</name> + <value> + <int>2</int> + </value> + </member> + <member> + <name>key</name> + <value> + <string>ssh-key</string> + </value> + </member> + <member> + <name>label</name> + <value> + <string>test2</string> + </value> + </member> + <member> + <name>fingerprint</name> + <value> + <string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string> + </value> + </member> + </struct> + </value> + </param> +</params> http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_createObject.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_createObject.xml b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_createObject.xml new file mode 100644 index 0000000..716832e --- /dev/null +++ b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_createObject.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<params> + <param> + <value> + <struct> + <member> + <name>id</name> + <value> + <int>1</int> + </value> + </member> + <member> + <name>key</name> + <value> + <string>ssh-key</string> + </value> + </member> + <member> + <name>label</name> + <value> + <string>my-key-pair</string> + </value> + </member> + <member> + <name>label</name> + <value> + <string>my-key-pair</string> + </value> + </member> + <member> + <name>fingerprint</name> + <value> + <string>1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f</string> + </value> + </member> + </struct> + </value> + </param> +</params> http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_deleteObject.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_deleteObject.xml b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_deleteObject.xml new file mode 100644 index 0000000..119680e --- /dev/null +++ b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_deleteObject.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<params> + <param> + <value> + <struct> + <member> + <name>status</name> + <value> + <string>success</string> + </value> + </member> + </struct> + </value> + </param> +</params> http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_getObject.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_getObject.xml b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_getObject.xml new file mode 100644 index 0000000..cf96523 --- /dev/null +++ b/libcloud/test/compute/fixtures/softlayer/v3__SoftLayer_Security_Ssh_Key_getObject.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<params> + <param> + <value> + <struct> + <member> + <name>id</name> + <value> + <int>1</int> + </value> + </member> + <member> + <name>key</name> + <value> + <string>ssh-key</string> + </value> + </member> + <member> + <name>label</name> + <value> + <string>test1</string> + </value> + </member> + <member> + <name>fingerprint</name> + <value> + <string>00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00</string> + </value> + </member> + </struct> + </value> + </param> +</params> http://git-wip-us.apache.org/repos/asf/libcloud/blob/f007edaa/libcloud/test/compute/test_softlayer.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_softlayer.py b/libcloud/test/compute/test_softlayer.py index 74f4a32..8f4536b 100644 --- a/libcloud/test/compute/test_softlayer.py +++ b/libcloud/test/compute/test_softlayer.py @@ -16,6 +16,13 @@ import unittest import sys +try: + import Crypto + Crypto + crypto = True +except ImportError: + crypto = False + from libcloud.common.types import InvalidCredsError from libcloud.utils.py3 import httplib @@ -25,12 +32,15 @@ from libcloud.utils.py3 import next from libcloud.compute.drivers.softlayer import SoftLayerNodeDriver as SoftLayer from libcloud.compute.drivers.softlayer import SoftLayerException, \ NODE_STATE_MAP -from libcloud.compute.types import NodeState +from libcloud.compute.types import NodeState, KeyPairDoesNotExistError from libcloud.test import MockHttp # pylint: disable-msg=E0611 from libcloud.test.file_fixtures import ComputeFileFixtures from libcloud.test.secrets import SOFTLAYER_PARAMS +null_fingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:' + \ + '00:00:00:00:00' + class SoftLayerTests(unittest.TestCase): @@ -119,6 +129,7 @@ class SoftLayerTests(unittest.TestCase): ex_cpus=2, ex_ram=2048, ex_disk=100, + ex_key='test1', ex_bandwidth=10, ex_local_disk=False, ex_datacenter='Dal05', @@ -132,6 +143,37 @@ class SoftLayerTests(unittest.TestCase): node = self.driver.list_nodes()[0] self.driver.destroy_node(node) + def test_list_keypairs(self): + keypairs = self.driver.list_key_pairs() + self.assertEqual(len(keypairs), 2) + self.assertEqual(keypairs[0].name, 'test1') + self.assertEqual(keypairs[0].fingerprint, null_fingerprint) + + def test_get_key_pair(self): + key_pair = self.driver.get_key_pair(name='test1') + self.assertEqual(key_pair.name, 'test1') + + def test_get_key_pair_does_not_exist(self): + self.assertRaises(KeyPairDoesNotExistError, self.driver.get_key_pair, + name='test-key-pair') + + def test_create_key_pair(self): + if crypto: + key_pair = self.driver.create_key_pair(name='my-key-pair') + fingerprint = ('1f:51:ae:28:bf:89:e9:d8:1f:25:5d' + ':37:2d:7d:b8:ca:9f:f5:f1:6f') + + self.assertEqual(key_pair.name, 'my-key-pair') + self.assertEqual(key_pair.fingerprint, fingerprint) + self.assertTrue(key_pair.private_key is not None) + else: + self.assertRaises(NotImplementedError, self.driver.create_key_pair, + name='my-key-pair') + + def test_delete_key_pair(self): + success = self.driver.delete_key_pair('test1') + self.assertTrue(success) + class SoftLayerMockHttp(MockHttp): fixtures = ComputeFileFixtures('softlayer') @@ -188,6 +230,26 @@ class SoftLayerMockHttp(MockHttp): body = self.fixtures.load('empty.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _xmlrpc_v3_SoftLayer_Account_getSshKeys( + self, method, url, body, headers): + body = self.fixtures.load('v3__SoftLayer_Account_getSshKeys.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_getObject( + self, method, url, body, headers): + body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_getObject.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_createObject( + self, method, url, body, headers): + body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_createObject.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _xmlrpc_v3_SoftLayer_Security_Ssh_Key_deleteObject( + self, method, url, body, headers): + body = self.fixtures.load('v3__SoftLayer_Security_Ssh_Key_deleteObject.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + if __name__ == '__main__': sys.exit(unittest.main())
