You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/12/10 22:08:35 UTC

[2/8] git commit: Update existing compute drivers (OpenStack, CloudStack, EC2) which expose key pair management through the extension methods to expose it through a new standard key pair management API.

Update existing compute drivers (OpenStack, CloudStack, EC2) which expose key
pair management through the extension methods to expose it through a new
standard key pair management API.

Also update affected code and tests, deprecate existing extension methods and
add some missing tests.

Note: Old and now deprecated methods will work until a next major release.


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/b398aebd
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/b398aebd
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/b398aebd

Branch: refs/heads/trunk
Commit: b398aebdbb50fbba06b46b466e95904c96c1ad99
Parents: eed9777
Author: Tomaz Muraus <to...@apache.org>
Authored: Fri Dec 6 15:36:09 2013 +0100
Committer: Tomaz Muraus <to...@apache.org>
Committed: Sun Dec 8 13:45:42 2013 +0100

----------------------------------------------------------------------
 libcloud/compute/drivers/cloudstack.py          | 290 +++++++++++++++----
 libcloud/compute/drivers/ec2.py                 | 196 +++++++++----
 libcloud/compute/drivers/openstack.py           |  96 ++++--
 .../cloudstack/createSSHKeyPair_default.json    |   1 +
 .../compute/fixtures/ec2/create_key_pair.xml    |  22 ++
 libcloud/test/compute/test_cloudstack.py        |  72 +++--
 libcloud/test/compute/test_ec2.py               |  57 +++-
 libcloud/test/compute/test_openstack.py         |  26 +-
 8 files changed, 567 insertions(+), 193 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/compute/drivers/cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/cloudstack.py b/libcloud/compute/drivers/cloudstack.py
index 27498fd..e9742fa 100644
--- a/libcloud/compute/drivers/cloudstack.py
+++ b/libcloud/compute/drivers/cloudstack.py
@@ -15,16 +15,17 @@
 
 from __future__ import with_statement
 
-import os
 import base64
+import warnings
 
 from libcloud.utils.py3 import b
 from libcloud.utils.py3 import urlparse
 
 from libcloud.compute.providers import Provider
 from libcloud.common.cloudstack import CloudStackDriverMixIn
-from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation,\
-    NodeSize, StorageVolume
+from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation
+from libcloud.compute.base import NodeSize, StorageVolume
+from libcloud.compute.base import KeyPair
 from libcloud.compute.types import NodeState, LibcloudError
 from libcloud.utils.networking import is_private_subnet
 
@@ -649,6 +650,147 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
                                 driver=self))
         return list_volumes
 
+    def list_key_pairs(self, **kwargs):
+        """
+        List registered key pairs.
+
+        :param     projectid: list objects by project
+        :type      projectid: ``str``
+
+        :param     page: The page to list the keypairs from
+        :type      page: ``int``
+
+        :param     keyword: List by keyword
+        :type      keyword: ``str``
+
+        :param     listall: If set to false, list only resources
+                            belonging to the command's caller;
+                            if set to true - list resources that
+                            the caller is authorized to see.
+                            Default value is false
+
+        :type      listall: ``bool``
+
+        :param     pagesize: The number of results per page
+        :type      pagesize: ``int``
+
+        :param     account: List resources by account.
+                            Must be used with the domainId parameter
+        :type      account: ``str``
+
+        :param     isrecursive: Defaults to false, but if true,
+                                lists all resources from
+                                the parent specified by the
+                                domainId till leaves.
+        :type      isrecursive: ``bool``
+
+        :param     fingerprint: A public key fingerprint to look for
+        :type      fingerprint: ``str``
+
+        :param     name: A key pair name to look for
+        :type      name: ``str``
+
+        :param     domainid: List only resources belonging to
+                                     the domain specified
+        :type      domainid: ``str``
+
+        :return:   A list of key par objects.
+        :rtype:   ``list`` of :class:`libcloud.compute.base.KeyPair`
+        """
+        extra_args = kwargs.copy()
+        res = self._sync_request(command='listSSHKeyPairs',
+                                 params=extra_args,
+                                 method='GET')
+        key_pairs = res.get('sshkeypair', [])
+        key_pairs = self._to_key_pairs(data=key_pairs)
+        return key_pairs
+
+    def create_key_pair(self, name, **kwargs):
+        """
+        Create a new key pair object.
+
+        :param name: Key pair name.
+        :type name: ``str``
+
+        :param     name: Name of the keypair (required)
+        :type      name: ``str``
+
+        :param     projectid: An optional project for the ssh key
+        :type      projectid: ``str``
+
+        :param     domainid: An optional domainId for the ssh key.
+                             If the account parameter is used,
+                             domainId must also be used.
+        :type      domainid: ``str``
+
+        :param     account: An optional account for the ssh key.
+                            Must be used with domainId.
+        :type      account: ``str``
+
+        :return:   Created key pair object.
+        :rtype:    :class:`libcloud.compute.base.KeyPair`
+        """
+        extra_args = kwargs.copy()
+
+        params = {'name': name}
+        params.update(extra_args)
+
+        res = self._sync_request(command='createSSHKeyPair',
+                                 params=params,
+                                 method='GET')
+        key_pair = self._to_key_pair(data=res['keypair'])
+        return key_pair
+
+    def import_key_pair_from_string(self, name, key_material):
+        """
+        Import a new public key from string.
+
+        :param name: Key pair name.
+        :type name: ``str``
+
+        :param key_material: Public key material.
+        :type key_material: ``str``
+
+        :return: Imported key pair object.
+        :rtype: :class:`libcloud.compute.base.KeyPair`
+        """
+        res = self._sync_request(command='registerSSHKeyPair',
+                                 params={'name': name,
+                                         'publickey': key_material},
+                                 method='GET')
+        key_pair = self._to_key_pair(data=res['keypair'])
+        return key_pair
+
+    def delete_key_pair(self, key_pair, **kwargs):
+        """
+        Delete an existing key pair.
+
+        :param key_pair: Key pair object.
+        :type key_pair: :class`libcloud.compute.base.KeyPair`
+
+        :param     projectid: The project associated with keypair
+        :type      projectid: ``str``
+
+        :param     domainid : The domain ID associated with the keypair
+        :type      domainid: ``str``
+
+        :param     account : The account associated with the keypair.
+                             Must be used with the domainId parameter.
+        :type      account: ``str``
+
+        :return:   True of False based on success of Keypair deletion
+        :rtype:    ``bool``
+        """
+
+        extra_args = kwargs.copy()
+        params = {'name': key_pair.name}
+        params.update(extra_args)
+
+        res = self._sync_request(command='deleteSSHKeyPair',
+                                 params=params,
+                                 method='GET')
+        return res['success'] == 'true'
+
     def ex_list_public_ips(self):
         """
         Lists all Public IP Addresses.
@@ -914,13 +1056,22 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         :return:   A list of keypair dictionaries
         :rtype:   ``list`` of ``dict``
         """
+        warnings.warn('This method has been deprecated in favor of '
+                      'list_key_pairs method')
 
-        extra_args = kwargs.copy()
-        res = self._sync_request(command='listSSHKeyPairs',
-                                 params=extra_args,
-                                 method='GET')
-        keypairs = res.get('sshkeypair', [])
-        return keypairs
+        key_pairs = self.list_key_pairs(**kwargs)
+
+        result = []
+
+        for key_pair in key_pairs:
+            item = {
+                'name': key_pair.name,
+                'fingerprint': key_pair.fingerprint,
+                'privateKey': key_pair.private_key
+            }
+            result.append(item)
+
+        return result
 
     def ex_create_keypair(self, name, **kwargs):
         """
@@ -944,50 +1095,18 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
         :return:   A keypair dictionary
         :rtype:    ``dict``
         """
-        extra_args = kwargs.copy()
-
-        for keypair in self.ex_list_keypairs():
-            if keypair['name'] == name:
-                raise LibcloudError('SSH KeyPair with name=%s already exists'
-                                    % (name))
-
-        params = {'name': name}
-        params.update(extra_args)
-
-        res = self._sync_request(command='createSSHKeyPair',
-                                 params=params,
-                                 method='GET')
-        return res['keypair']
-
-    def ex_delete_keypair(self, keypair, **kwargs):
-        """
-        Deletes an existing SSH KeyPair
-
-        :param     keypair: Name of the keypair (required)
-        :type      keypair: ``str``
-
-        :param     projectid: The project associated with keypair
-        :type      projectid: ``str``
-
-        :param     domainid : The domain ID associated with the keypair
-        :type      domainid: ``str``
+        warnings.warn('This method has been deprecated in favor of '
+                      'create_key_pair method')
 
-        :param     account : The account associated with the keypair.
-                             Must be used with the domainId parameter.
-        :type      account: ``str``
-
-        :return:   True of False based on success of Keypair deletion
-        :rtype:    ``bool``
-        """
+        key_pair = self.create_key_pair(name=name, **kwargs)
 
-        extra_args = kwargs.copy()
-        params = {'name': keypair}
-        params.update(extra_args)
+        result = {
+            'name': key_pair.name,
+            'fingerprint': key_pair.fingerprint,
+            'privateKey': key_pair.private_key
+        }
 
-        res = self._sync_request(command='deleteSSHKeyPair',
-                                 params=params,
-                                 method='GET')
-        return res['success']
+        return result
 
     def ex_import_keypair_from_string(self, name, key_material):
         """
@@ -1001,15 +1120,18 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
 
         :rtype: ``dict``
         """
-        res = self._sync_request(command='registerSSHKeyPair',
-                                 params={'name': name,
-                                         'publickey': key_material},
-                                 method='GET')
-        return {
-            'keyName': res['keypair']['name'],
-            'keyFingerprint': res['keypair']['fingerprint']
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_string method')
+
+        key_pair = self.import_key_pair_from_string(name=name,
+                                                    key_material=key_material)
+        result = {
+            'keyName': key_pair.name,
+            'keyFingerprint': key_pair.fingerprint
         }
 
+        return result
+
     def ex_import_keypair(self, name, keyfile):
         """
         Imports a new public key where the public key is passed via a filename
@@ -1022,9 +1144,45 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
 
         :rtype: ``dict``
         """
-        with open(os.path.expanduser(keyfile)) as fh:
-            content = fh.read()
-        return self.ex_import_keypair_from_string(name, content)
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_file method')
+
+        key_pair = self.import_key_pair_from_file(name=name,
+                                                  key_file_path=keyfile)
+        result = {
+            'keyName': key_pair.name,
+            'keyFingerprint': key_pair.fingerprint
+        }
+
+        return result
+
+    def ex_delete_keypair(self, keypair, **kwargs):
+        """
+        Deletes an existing SSH KeyPair
+
+        :param     keypair: Name of the keypair (required)
+        :type      keypair: ``str``
+
+        :param     projectid: The project associated with keypair
+        :type      projectid: ``str``
+
+        :param     domainid : The domain ID associated with the keypair
+        :type      domainid: ``str``
+
+        :param     account : The account associated with the keypair.
+                             Must be used with the domainId parameter.
+        :type      account: ``str``
+
+        :return:   True of False based on success of Keypair deletion
+        :rtype:    ``bool``
+        """
+        warnings.warn('This method has been deprecated in favor of '
+                      'delete_key_pair method')
+
+        key_pair = KeyPair(name=keypair, public_key=None, fingerprint=None,
+                           driver=self)
+
+        return self.delete_key_pair(key_pair=key_pair)
 
     def ex_list_security_groups(self, **kwargs):
         """
@@ -1313,3 +1471,15 @@ class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
                               public_ips=public_ips, private_ips=private_ips,
                               driver=self, extra=extra)
         return node
+
+    def _to_key_pairs(self, data):
+        key_pairs = [self._to_key_pair(data=item) for item in data]
+        return key_pairs
+
+    def _to_key_pair(self, data):
+        key_pair = KeyPair(name=data['name'],
+                           fingerprint=data['fingerprint'],
+                           public_key=data.get('publicKey', None),
+                           private_key=data.get('privateKey', None),
+                           driver=self)
+        return key_pair

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index c79d6fe..3db3c35 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -17,12 +17,10 @@
 Amazon EC2, Eucalyptus and Nimbus drivers.
 """
 
-from __future__ import with_statement
-
 import sys
 import base64
-import os
 import copy
+import warnings
 
 from xml.etree import ElementTree as ET
 
@@ -39,6 +37,7 @@ from libcloud.compute.providers import Provider
 from libcloud.compute.types import NodeState
 from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize
 from libcloud.compute.base import NodeImage, StorageVolume, VolumeSnapshot
+from libcloud.compute.base import KeyPair
 
 API_VERSION = '2010-08-31'
 NAMESPACE = 'http://ec2.amazonaws.com/doc/%s/' % (API_VERSION)
@@ -912,6 +911,71 @@ class BaseEC2NodeDriver(NodeDriver):
         response = self.connection.request(self.path, params=params).object
         return self._get_boolean(response)
 
+    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):
+        name = findtext(element=elem, xpath='keyName', namespace=NAMESPACE)
+        fingerprint = findtext(element=elem, xpath='keyFingerprint',
+                               namespace=NAMESPACE).strip()
+        private_key = findtext(element=elem, xpath='keyMaterial',
+                               namespace=NAMESPACE)
+
+        key_pair = KeyPair(name=name,
+                           public_key=None,
+                           fingerprint=fingerprint,
+                           private_key=private_key,
+                           driver=self)
+        return key_pair
+
+    def list_key_pairs(self):
+        params = {
+            'Action': 'DescribeKeyPairs'
+        }
+
+        response = self.connection.request(self.path, params=params)
+        elems = findall(element=response.object, xpath='keySet/item',
+                        namespace=NAMESPACE)
+
+        key_pairs = self._to_key_pairs(elems=elems)
+        return key_pairs
+
+    def create_key_pair(self, name):
+        params = {
+            'Action': 'CreateKeyPair',
+            'KeyName': name
+        }
+
+        response = self.connection.request(self.path, params=params)
+        elem = response.object
+        key_pair = self._to_key_pair(elem=elem)
+        return key_pair
+
+    def import_key_pair_from_string(self, name, key_material):
+        base64key = base64.b64encode(b(key_material))
+
+        params = {
+            'Action': 'ImportKeyPair',
+            'KeyName': name,
+            'PublicKeyMaterial': base64key
+        }
+
+        response = self.connection.request(self.path, params=params)
+        elem = response.object
+        key_pair = self._to_key_pair(elem=elem)
+        return key_pair
+
+    def delete_key_pair(self, key_pair):
+        params = {
+            'Action': 'DeleteKeyPair',
+            'KeyName': key_pair
+        }
+        result = self.connection.request(self.path, params=params).object
+        element = findtext(element=result, xpath='return',
+                           namespace=NAMESPACE)
+        return element == 'true'
+
     def ex_destroy_image(self, image):
         params = {
             'Action': 'DeregisterImage',
@@ -921,7 +985,8 @@ class BaseEC2NodeDriver(NodeDriver):
         return self._get_boolean(response)
 
     def ex_create_keypair(self, name):
-        """Creates a new keypair
+        """
+        Creates a new keypair
 
         @note: This is a non-standard extension API, and only works for EC2.
 
@@ -931,20 +996,18 @@ class BaseEC2NodeDriver(NodeDriver):
 
         :rtype: ``dict``
         """
-        params = {
-            'Action': 'CreateKeyPair',
-            'KeyName': name,
-        }
-        response = self.connection.request(self.path, params=params).object
-        key_material = findtext(element=response, xpath='keyMaterial',
-                                namespace=NAMESPACE)
-        key_fingerprint = findtext(element=response, xpath='keyFingerprint',
-                                   namespace=NAMESPACE)
-        return {
-            'keyMaterial': key_material,
-            'keyFingerprint': key_fingerprint,
+        warnings.warn('This method has been deprecated in favor of '
+                      'create_key_pair method')
+
+        key_pair = self.create_key_pair(name=name)
+
+        result = {
+            'keyMaterial': key_pair.private_key,
+            'keyFingerprint': key_pair.fingerprint
         }
 
+        return result
+
     def ex_delete_keypair(self, keypair):
         """
         Delete a key pair by name.
@@ -956,14 +1019,10 @@ class BaseEC2NodeDriver(NodeDriver):
 
         :rtype: ``bool``
         """
-        params = {
-            'Action': 'DeleteKeyPair',
-            'KeyName': keypair
-        }
-        result = self.connection.request(self.path, params=params).object
-        element = findtext(element=result, xpath='return',
-                           namespace=NAMESPACE)
-        return element == 'true'
+        warnings.warn('This method has been deprecated in favor of '
+                      'delete_key_pair method')
+
+        return self.delete_key_pair(name=keypair)
 
     def ex_import_keypair_from_string(self, name, key_material):
         """
@@ -980,23 +1039,17 @@ class BaseEC2NodeDriver(NodeDriver):
 
         :rtype: ``dict``
         """
-        base64key = base64.b64encode(b(key_material))
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_string method')
 
-        params = {
-            'Action': 'ImportKeyPair',
-            'KeyName': name,
-            'PublicKeyMaterial': base64key
-        }
+        key_pair = self.import_key_pair_from_string(name=name,
+                                                    key_material=key_material)
 
-        response = self.connection.request(self.path, params=params).object
-        key_name = findtext(element=response, xpath='keyName',
-                            namespace=NAMESPACE)
-        key_fingerprint = findtext(element=response, xpath='keyFingerprint',
-                                   namespace=NAMESPACE)
-        return {
-            'keyName': key_name,
-            'keyFingerprint': key_fingerprint,
+        result = {
+            'keyName': key_pair.name,
+            'keyFingerprint': key_pair.fingerprint
         }
+        return result
 
     def ex_import_keypair(self, name, keyfile):
         """
@@ -1013,9 +1066,17 @@ class BaseEC2NodeDriver(NodeDriver):
 
         :rtype: ``dict``
         """
-        with open(os.path.expanduser(keyfile)) as fh:
-            content = fh.read()
-        return self.ex_import_keypair_from_string(name, content)
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_file method')
+
+        key_pair = self.import_key_pair_from_file(name=name,
+                                                  key_file_path=keyfile)
+
+        result = {
+            'keyName': key_pair.name,
+            'keyFingerprint': key_pair.fingerprint
+        }
+        return result
 
     def ex_find_or_import_keypair_by_key_material(self, pubkey):
         """
@@ -1023,16 +1084,27 @@ class BaseEC2NodeDriver(NodeDriver):
         exists, return any information we have about it. Otherwise, create it.
 
         Keys that are created are named based on their comment and fingerprint.
+
+        :rtype: ``dict``
         """
         key_fingerprint = get_pubkey_ssh2_fingerprint(pubkey)
         key_comment = get_pubkey_comment(pubkey, default='unnamed')
-        key_name = "%s-%s" % (key_comment, key_fingerprint)
+        key_name = '%s-%s' % (key_comment, key_fingerprint)
 
-        for keypair in self.ex_list_keypairs():
-            if keypair['keyFingerprint'] == key_fingerprint:
-                return keypair
+        key_pairs = self.list_key_pairs()
+        key_pairs = [key_pair for key_pair in key_pairs if
+                     key_pair.fingerprint == key_fingerprint]
 
-        return self.ex_import_keypair_from_string(key_name, pubkey)
+        if len(key_pairs) >= 1:
+            key_pair = key_pairs[0]
+            result = {
+                'keyName': key_pair.name,
+                'keyFingerprint': key_pair.fingerprint
+            }
+        else:
+            result = self.ex_import_keypair_from_string(key_name, pubkey)
+
+        return result
 
     def ex_list_keypairs(self):
         """
@@ -1040,34 +1112,32 @@ class BaseEC2NodeDriver(NodeDriver):
 
         :rtype: ``list`` of ``dict``
         """
-        params = {
-            'Action': 'DescribeKeyPairs'
-        }
+        warnings.warn('This method has been deprecated in favor of '
+                      'list_key_pairs method')
 
-        response = self.connection.request(self.path, params=params).object
-        keypairs = []
-        for elem in findall(element=response, xpath='keySet/item',
-                            namespace=NAMESPACE):
-            keypair = {
-                'keyName': findtext(element=elem, xpath='keyName',
-                                    namespace=NAMESPACE),
-                'keyFingerprint': findtext(element=elem,
-                                           xpath='keyFingerprint',
-                                           namespace=NAMESPACE).strip(),
+        key_pairs = self.list_key_pairs()
+
+        result = []
+
+        for key_pair in key_pairs:
+            item = {
+                'keyName': key_pair.name,
+                'keyFingerprint': key_pair.fingerprint,
             }
-            keypairs.append(keypair)
+            result.append(item)
 
-        return keypairs
+        return result
 
     def ex_describe_all_keypairs(self):
         """
-        Describes all keypairs. This is here for backward compatibilty.
+        Return names for all the available key pairs.
 
         @note: This is a non-standard extension API, and only works for EC2.
 
         :rtype: ``list`` of ``str``
         """
-        return [k['keyName'] for k in self.ex_list_keypairs()]
+        names = [key_pair.name for key_pair in self.list_key_pairs()]
+        return names
 
     def ex_describe_keypairs(self, name):
         """

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/compute/drivers/openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py
index 7d0958a..1866120 100644
--- a/libcloud/compute/drivers/openstack.py
+++ b/libcloud/compute/drivers/openstack.py
@@ -16,23 +16,19 @@
 OpenStack driver
 """
 
-from __future__ import with_statement
-
 try:
     import simplejson as json
 except ImportError:
     import json
 
 import warnings
+import base64
 
 from libcloud.utils.py3 import httplib
 from libcloud.utils.py3 import b
 from libcloud.utils.py3 import next
 from libcloud.utils.py3 import urlparse
 
-import os
-import base64
-
 from xml.etree import ElementTree as ET
 
 from libcloud.common.openstack import OpenStackBaseConnection
@@ -41,6 +37,7 @@ from libcloud.common.types import MalformedResponseError, ProviderError
 from libcloud.compute.types import NodeState, Provider
 from libcloud.compute.base import NodeSize, NodeImage
 from libcloud.compute.base import NodeDriver, Node, NodeLocation, StorageVolume
+from libcloud.compute.base import KeyPair
 from libcloud.pricing import get_size_price
 from libcloud.common.base import Response
 from libcloud.utils.xml import findall
@@ -1710,16 +1707,51 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
                                        (rule.id), method='DELETE')
         return resp.status == httplib.NO_CONTENT
 
-    def _to_keypairs(self, obj):
-        keypairs = obj['keypairs']
-        return [self._to_keypair(keypair['keypair']) for keypair in keypairs]
+    def _to_key_pairs(self, obj):
+        key_pairs = obj['keypairs']
+        key_pairs = [self._to_key_pair(key_pair['keypair']) for key_pair in
+                     key_pairs]
+        return key_pairs
+
+    def _to_key_pair(self, obj):
+        key_pair = KeyPair(name=obj['name'],
+                           fingerprint=obj['fingerprint'],
+                           public_key=obj['public_key'],
+                           private_key=obj.get('private_key', None),
+                           driver=self)
+        return key_pair
+
+    def list_key_pairs(self):
+        response = self.connection.request('/os-keypairs')
+        key_pairs = self._to_key_pairs(response.object)
+        return key_pairs
+
+    def create_key_pair(self, name):
+        data = {'keypair': {'name': name}}
+        response = self.connection.request('/os-keypairs', method='POST',
+                                           data=data)
+        key_pair = self._to_key_pair(response.object['keypair'])
+        return key_pair
+
+    def import_key_pair_from_string(self, name, key_material):
+        data = {'keypair': {'name': name, 'public_key': key_material}}
+        response = self.connection.request('/os-keypairs', method='POST',
+                                           data=data)
+        key_pair = self._to_key_pair(response.object['keypair'])
+        return key_pair
+
+    def delete_key_pair(self, key_pair):
+        """
+        Delete a KeyPair.
 
-    def _to_keypair(self, obj):
-        return OpenStackKeyPair(name=obj['name'],
-                                fingerprint=obj['fingerprint'],
-                                public_key=obj['public_key'],
-                                private_key=obj.get('private_key', None),
-                                driver=self)
+        :param keypair: KeyPair to delete
+        :type  keypair: :class:`OpenStackKeyPair`
+
+        :rtype: ``bool``
+        """
+        response = self.connection.request('/os-keypairs/%s' % (key_pair.name),
+                                           method='DELETE')
+        return response.status == httplib.ACCEPTED
 
     def ex_list_keypairs(self):
         """
@@ -1727,8 +1759,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
 
         :rtype: ``list`` of :class:`OpenStackKeyPair`
         """
-        return self._to_keypairs(
-            self.connection.request('/os-keypairs').object)
+        warnings.warn('This method has been deprecated in favor of '
+                      'list_key_pairs method')
+
+        return self.list_key_pairs()
 
     def ex_create_keypair(self, name):
         """
@@ -1739,10 +1773,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
 
         :rtype: :class:`OpenStackKeyPair`
         """
-        return self._to_keypair(self.connection.request(
-            '/os-keypairs', method='POST',
-            data={'keypair': {'name': name}}
-        ).object['keypair'])
+        warnings.warn('This method has been deprecated in favor of '
+                      'create_key_pair method')
+
+        return self.create_key_pair(name=name)
 
     def ex_import_keypair(self, name, keyfile):
         """
@@ -1756,10 +1790,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
 
         :rtype: :class:`OpenStackKeyPair`
         """
-        with open(os.path.expanduser(keyfile), 'r') as fp:
-            public_key = fp.read()
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_file method')
 
-        return self.ex_import_keypair_from_string(name, public_key)
+        return self.import_key_pair_from_file(name=name, key_file_path=keyfile)
 
     def ex_import_keypair_from_string(self, name, key_material):
         """
@@ -1773,10 +1807,11 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
 
         :rtype: :class:`OpenStackKeyPair`
         """
-        return self._to_keypair(self.connection.request(
-            '/os-keypairs', method='POST',
-            data={'keypair': {'name': name, 'public_key': key_material}}
-        ).object['keypair'])
+        warnings.warn('This method has been deprecated in favor of '
+                      'import_key_pair_from_string method')
+
+        return self.import_key_pair_from_string(name=name,
+                                                key_material=key_material)
 
     def ex_delete_keypair(self, keypair):
         """
@@ -1787,9 +1822,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
 
         :rtype: ``bool``
         """
-        resp = self.connection.request('/os-keypairs/%s' % (keypair.name),
-                                       method='DELETE')
-        return resp.status == httplib.ACCEPTED
+        warnings.warn('This method has been deprecated in favor of '
+                      'delete_key_pair method')
+
+        return self.delete_key_pair(key_pair=keypair)
 
     def ex_get_size(self, size_id):
         """

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/test/compute/fixtures/cloudstack/createSSHKeyPair_default.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/cloudstack/createSSHKeyPair_default.json b/libcloud/test/compute/fixtures/cloudstack/createSSHKeyPair_default.json
new file mode 100644
index 0000000..2d0740d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/cloudstack/createSSHKeyPair_default.json
@@ -0,0 +1 @@
+{ "createsshkeypairresponse" :  { "keypair" : {"name":"test-keypair","fingerprint":"51:9f:81:30:ec:82:0c:e5:8c:81:ac:14:27:d0:e5:e2","privateKey":"-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDMaSZY4v228AWWcXYLoojgaZ+K8SbuI8YoPDEi9UWcww5mWSTx\nVl6Ksb8YPFxL6+3/unlfr4zK1LksxgN8XRuZr+YBFGphUB6a5EcyshkXi3mfAE7d\n6a26ah6ySXFK9GmZoXcJqQ1xLC9rKGPL7tWgHmbX1lCbN6QinV0mZVEHNwIDAQAB\nAoGACXQngN7mqwpIx99xfTJEMFTSOyPSEBt5c6zs/NfpI0nmJZej3MGI19NGqkFI\nZ35+4F/ocyN0WIEkG00BJkRMHWdPNd+YnVSuVgEyGCD8hDvBbUEQrmdZ0VfQt+2q\nd52g573s6D6Skk/SZHGi3yHca4H52c3EpLJzThxUmJSSqmECQQD0loEIiQzQaap3\n/Gce7nZeLCSNXf0Q5aKFQv/X22srw6YvJ9/25cLahiFtQUadId9VUXSYTgEKX0ST\nB2CZ4UJxAkEA1fK/PT+YIHaiQIiCK/xTnoIuTvdXmH0IozolRxGAKpQZNvaMpKgn\nvXU84/yztekEPG0pKmCm7CZUZoGdfiJoJwJALwUsAy8NtpdJvU1ZqbmgKdSEpmS2\nPORYjRPnSWEWRlCThyc8SCO9hPMaQ/2zjIuxep5xMsJ0MsFD1pwpdwu2EQJBAMrG\nEZ7ZQTOzfMAxIT7THeWjeIR7RNhP2PnrSB19Zr30M5m2P0Jn5ZJZJWbnwOPuf4dN\n5rA1fr9e7KtiuYQs1A0CQQCT06qHdHaQr78A6YTEbDVr0M57qVrdsm5xyXzCmpMy\n9LxXAACghjHbjF//FEOjNG21I
 utbCg6cNIRz5EM8+MD+\n-----END RSA PRIVATE KEY-----\n"} }  }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/test/compute/fixtures/ec2/create_key_pair.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/create_key_pair.xml b/libcloud/test/compute/fixtures/ec2/create_key_pair.xml
new file mode 100644
index 0000000..10aec6d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/create_key_pair.xml
@@ -0,0 +1,22 @@
+<CreateKeyPairResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/">
+  <keyName>my-key-pair</keyName>
+  <keyFingerprint>
+     1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:7d:b8:ca:9f:f5:f1:6f
+  </keyFingerprint>
+  <keyMaterial>---- BEGIN RSA PRIVATE KEY ----
+MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6
+b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAd
+BgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcN
+MTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYD
+VQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25z
+b2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFt
+YXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ
+21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9T
+rDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpE
+Ibb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4
+nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0Fkb
+FFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTb
+NYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE
+-----END RSA PRIVATE KEY-----</keyMaterial>
+</CreateKeyPairResponse>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/test/compute/test_cloudstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_cloudstack.py b/libcloud/test/compute/test_cloudstack.py
index f7c99a4..65a0c28 100644
--- a/libcloud/test/compute/test_cloudstack.py
+++ b/libcloud/test/compute/test_cloudstack.py
@@ -271,51 +271,81 @@ class CloudStackCommonTestCase(TestCaseMixin):
         res = node.reboot()
         self.assertTrue(res)
 
-    def test_ex_list_keypairs(self):
-        keypairs = self.driver.ex_list_keypairs()
+    def test_list_key_pairs(self):
+        keypairs = self.driver.list_key_pairs()
         fingerprint = '00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:' + \
                       '00:00:00:00:00'
 
+        self.assertEqual(keypairs[0].name, 'cs-keypair')
+        self.assertEqual(keypairs[0].fingerprint, fingerprint)
+
+        # Test old and deprecated way
+        keypairs = self.driver.ex_list_keypairs()
+
         self.assertEqual(keypairs[0]['name'], 'cs-keypair')
         self.assertEqual(keypairs[0]['fingerprint'], fingerprint)
 
-    def test_ex_list_keypairs_no_keypair_key(self):
+    def test_list_key_pairs_no_keypair_key(self):
         CloudStackMockHttp.fixture_tag = 'no_keys'
-        keypairs = self.driver.ex_list_keypairs()
+        keypairs = self.driver.list_key_pairs()
         self.assertEqual(keypairs, [])
 
-    def test_ex_create_keypair(self):
-        self.assertRaises(
-            LibcloudError,
-            self.driver.ex_create_keypair,
-            'cs-keypair')
+    def test_create_keypair(self):
+        key_pair = self.driver.create_key_pair(name='test-keypair')
 
-    def test_ex_delete_keypair(self):
-        res = self.driver.ex_delete_keypair('cs-keypair')
-        self.assertTrue(res)
+        self.assertEqual(key_pair.name, 'test-keypair')
+        self.assertTrue(key_pair.fingerprint is not None)
+        self.assertTrue(key_pair.private_key is not None)
+
+        # Test old and deprecated way
+        res = self.driver.ex_create_keypair(name='test-keypair')
+        self.assertEqual(res['name'], 'test-keypair')
+        self.assertTrue(res['fingerprint'] is not None)
+        self.assertTrue(res['privateKey'] is not None)
 
-    def test_ex_import_keypair(self):
+    def test_import_keypair_from_file(self):
         fingerprint = 'c4:a1:e5:d4:50:84:a9:4c:6b:22:ee:d6:57:02:b8:15'
-        path = os.path.join(os.path.dirname(__file__), "fixtures",
-                            "cloudstack",
-                            "dummy_rsa.pub")
+        path = os.path.join(os.path.dirname(__file__), 'fixtures',
+                            'cloudstack',
+                            'dummy_rsa.pub')
 
+        key_pair = self.driver.import_key_pair_from_file('foobar', path)
+        self.assertEqual(key_pair.name, 'foobar')
+        self.assertEqual(key_pair.fingerprint, fingerprint)
+
+        # Test old and deprecated way
         res = self.driver.ex_import_keypair('foobar', path)
         self.assertEqual(res['keyName'], 'foobar')
         self.assertEqual(res['keyFingerprint'], fingerprint)
 
     def test_ex_import_keypair_from_string(self):
         fingerprint = 'c4:a1:e5:d4:50:84:a9:4c:6b:22:ee:d6:57:02:b8:15'
-        path = os.path.join(os.path.dirname(__file__), "fixtures",
-                            "cloudstack",
-                            "dummy_rsa.pub")
+        path = os.path.join(os.path.dirname(__file__), 'fixtures',
+                            'cloudstack',
+                            'dummy_rsa.pub')
         fh = open(path)
-        res = self.driver.ex_import_keypair_from_string('foobar',
-                                                        fh.read())
+        key_material = fh.read()
         fh.close()
+
+        key_pair = self.driver.import_key_pair_from_string('foobar', key_material=key_material)
+        self.assertEqual(key_pair.name, 'foobar')
+        self.assertEqual(key_pair.fingerprint, fingerprint)
+
+        # Test old and deprecated way
+        res = self.driver.ex_import_keypair_from_string('foobar', key_material=key_material)
         self.assertEqual(res['keyName'], 'foobar')
         self.assertEqual(res['keyFingerprint'], fingerprint)
 
+    def test_delete_key_pair(self):
+        key_pair = self.driver.list_key_pairs()[0]
+
+        res = self.driver.delete_key_pair(key_pair=key_pair)
+        self.assertTrue(res)
+
+        # Test old and deprecated way
+        res = self.driver.ex_delete_keypair(keypair='cs-keypair')
+        self.assertTrue(res)
+
     def test_ex_list_security_groups(self):
         groups = self.driver.ex_list_security_groups()
         self.assertEqual(2, len(groups))

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/test/compute/test_ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py
index 3952c68..c32bffe 100644
--- a/libcloud/test/compute/test_ec2.py
+++ b/libcloud/test/compute/test_ec2.py
@@ -375,13 +375,35 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(availability_zone.zone_state, 'available')
         self.assertEqual(availability_zone.region_name, 'eu-west-1')
 
-    def test_ex_list_keypairs(self):
+    def test_list_keypairs(self):
+        keypairs = self.driver.list_key_pairs()
+
+        self.assertEqual(len(keypairs), 1)
+        self.assertEqual(keypairs[0].name, 'gsg-keypair')
+        self.assertEqual(keypairs[0].fingerprint, null_fingerprint)
+
+        # Test old deprecated method
         keypairs = self.driver.ex_list_keypairs()
 
         self.assertEqual(len(keypairs), 1)
         self.assertEqual(keypairs[0]['keyName'], 'gsg-keypair')
         self.assertEqual(keypairs[0]['keyFingerprint'], null_fingerprint)
 
+    def test_create_key_pair(self):
+        key_pair = self.driver.create_key_pair(name='test-keypair')
+
+        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)
+
+        # Test old and deprecated method
+        key_pair = self.driver.ex_create_keypair(name='test-keypair')
+        self.assertEqual(key_pair['keyFingerprint'], fingerprint)
+        self.assertTrue(key_pair['keyMaterial'] is not None)
+
     def test_ex_describe_all_keypairs(self):
         keys = self.driver.ex_describe_all_keypairs()
         self.assertEqual(keys, ['gsg-keypair'])
@@ -397,7 +419,11 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(keypair2['keyName'], 'gsg-keypair')
         self.assertEqual(keypair2['keyFingerprint'], null_fingerprint)
 
-    def ex_delete_keypair(self):
+    def ex_delete_key_pair(self):
+        success = self.driver.delete_key_pair('testkey')
+        self.assertTrue(success)
+
+        # Test old and deprecated method
         resp = self.driver.ex_delete_keypair('testkey')
         self.assertTrue(resp)
 
@@ -410,20 +436,33 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
         self.assertTrue('owner' in tags)
         self.assertTrue('stack' in tags)
 
-    def test_ex_import_keypair_from_string(self):
+    def test_import_key_pair_from_string(self):
         path = os.path.join(os.path.dirname(__file__), 'fixtures', 'misc',
                             'dummy_rsa.pub')
 
-        with open(path, 'r') as fh:
-            key = self.driver.ex_import_keypair_from_string(
-                'keypair', fh.read())
+        with open(path, 'r') as fp:
+            key_material = fp.read()
+
+        key = self.driver.import_key_pair_from_string(name='keypair',
+                                                      key_material=key_material)
+        self.assertEqual(key.name, 'keypair')
+        self.assertEqual(key.fingerprint, null_fingerprint)
 
+        # Test old and deprecated method
+        key = self.driver.ex_import_keypair_from_string('keypair',
+                                                        key_material)
         self.assertEqual(key['keyName'], 'keypair')
         self.assertEqual(key['keyFingerprint'], null_fingerprint)
 
-    def test_ex_import_keypair(self):
+    def test_import_key_pair_from_file(self):
         path = os.path.join(os.path.dirname(__file__), 'fixtures', 'misc',
                             'dummy_rsa.pub')
+
+        key = self.driver.import_key_pair_from_file('keypair', path)
+        self.assertEqual(key.name, 'keypair')
+        self.assertEqual(key.fingerprint, null_fingerprint)
+
+        # Test old and deprecated method
         key = self.driver.ex_import_keypair('keypair', path)
         self.assertEqual(key['keyName'], 'keypair')
         self.assertEqual(key['keyFingerprint'], null_fingerprint)
@@ -803,6 +842,10 @@ class EC2MockHttp(MockHttpTestCase):
         body = self.fixtures.load('describe_key_pairs.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _CreateKeyPair(self, method, url, body, headers):
+        body = self.fixtures.load('create_key_pair.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _ImportKeyPair(self, method, url, body, headers):
         body = self.fixtures.load('import_key_pair.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])

http://git-wip-us.apache.org/repos/asf/libcloud/blob/b398aebd/libcloud/test/compute/test_openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py
index b0e1d2c..3c8377b 100644
--- a/libcloud/test/compute/test_openstack.py
+++ b/libcloud/test/compute/test_openstack.py
@@ -1327,8 +1327,8 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
         result = self.driver.ex_delete_security_group_rule(security_group_rule)
         self.assertTrue(result)
 
-    def test_ex_list_keypairs(self):
-        keypairs = self.driver.ex_list_keypairs()
+    def test_list_key_pairs(self):
+        keypairs = self.driver.list_key_pairs()
         self.assertEqual(len(keypairs), 2, 'Wrong keypairs count')
         keypair = keypairs[1]
         self.assertEqual(keypair.name, 'key2')
@@ -1337,9 +1337,9 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
         self.assertTrue(len(keypair.public_key) > 10)
         self.assertEqual(keypair.private_key, None)
 
-    def test_ex_create_keypair(self):
+    def test_create_key_pair(self):
         name = 'key0'
-        keypair = self.driver.ex_create_keypair(name)
+        keypair = self.driver.create_key_pair(name=name)
         self.assertEqual(keypair.name, name)
 
         self.assertEqual(keypair.fingerprint,
@@ -1347,34 +1347,36 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
         self.assertTrue(len(keypair.public_key) > 10)
         self.assertTrue(len(keypair.private_key) > 10)
 
-    def test_ex_import_keypair(self):
+    def test_import_key_pair_from_file(self):
         name = 'key3'
         path = os.path.join(
-            os.path.dirname(__file__), "fixtures", "misc", "dummy_rsa.pub")
+            os.path.dirname(__file__), 'fixtures', 'misc', 'dummy_rsa.pub')
         pub_key = open(path, 'r').read()
-        keypair = self.driver.ex_import_keypair(name, path)
+        keypair = self.driver.import_key_pair_from_file(name=name,
+                                                        key_file_path=path)
         self.assertEqual(keypair.name, name)
         self.assertEqual(
             keypair.fingerprint, '97:10:a6:e7:92:65:7e:69:fe:e6:81:8f:39:3c:8f:5a')
         self.assertEqual(keypair.public_key, pub_key)
         self.assertEqual(keypair.private_key, None)
 
-    def test_ex_import_keypair_from_string(self):
+    def test_import_key_pair_from_string(self):
         name = 'key3'
         path = os.path.join(
-            os.path.dirname(__file__), "fixtures", "misc", "dummy_rsa.pub")
+            os.path.dirname(__file__), 'fixtures', 'misc', 'dummy_rsa.pub')
         pub_key = open(path, 'r').read()
-        keypair = self.driver.ex_import_keypair_from_string(name, pub_key)
+        keypair = self.driver.import_key_pair_from_string(name=name,
+                                                          key_material=pub_key)
         self.assertEqual(keypair.name, name)
         self.assertEqual(
             keypair.fingerprint, '97:10:a6:e7:92:65:7e:69:fe:e6:81:8f:39:3c:8f:5a')
         self.assertEqual(keypair.public_key, pub_key)
         self.assertEqual(keypair.private_key, None)
 
-    def test_ex_delete_keypair(self):
+    def test_delete_key_pair(self):
         keypair = OpenStackKeyPair(
             name='key1', fingerprint=None, public_key=None, driver=self.driver)
-        result = self.driver.ex_delete_keypair(keypair)
+        result = self.driver.delete_key_pair(key_pair=keypair)
         self.assertTrue(result)
 
     def test_ex_list_floating_ip_pools(self):