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 2015/06/14 12:52:20 UTC

[09/21] libcloud git commit: DO NodeDriver uses DO BaseDriver - Support for v1 and v2 initialization with authentication secret - Updated v1 DigitalOceanNodeDriver to use updated KeyPair Management - Updated tests to reflect KeyPair Management changes

DO NodeDriver uses DO BaseDriver - Support for v1 and v2 initialization with authentication secret - Updated v1 DigitalOceanNodeDriver to use updated KeyPair Management - Updated tests to reflect KeyPair Management changes

Signed-off-by: Tomaz Muraus <to...@tomaz.me>


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

Branch: refs/heads/trunk
Commit: 9224e8e180133f4af499a77366d3ca2d640afeda
Parents: d1a8e22
Author: Javier Castillo II <j....@gmail.com>
Authored: Sun Apr 12 18:02:07 2015 +0000
Committer: Tomaz Muraus <to...@tomaz.me>
Committed: Sun Jun 14 18:05:58 2015 +0800

----------------------------------------------------------------------
 libcloud/compute/drivers/digitalocean.py      | 172 +++++++++------------
 libcloud/test/compute/test_digitalocean_v1.py |  14 +-
 2 files changed, 79 insertions(+), 107 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/9224e8e1/libcloud/compute/drivers/digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/digitalocean.py b/libcloud/compute/drivers/digitalocean.py
index 32dd199..1cb209b 100644
--- a/libcloud/compute/drivers/digitalocean.py
+++ b/libcloud/compute/drivers/digitalocean.py
@@ -20,6 +20,8 @@ from libcloud.utils.py3 import httplib
 
 from libcloud.common.base import ConnectionUserAndKey, ConnectionKey
 from libcloud.common.base import JsonResponse
+from libcloud.common.digitalocean import DigitalOcean_v1_BaseDriver
+from libcloud.common.digitalocean import DigitalOcean_v2_BaseDriver
 from libcloud.compute.types import Provider, NodeState, InvalidCredsError
 from libcloud.compute.base import NodeDriver, Node
 from libcloud.compute.base import NodeImage, NodeSize, NodeLocation, KeyPair
@@ -27,7 +29,7 @@ from libcloud.compute.base import NodeImage, NodeSize, NodeLocation, KeyPair
 __all__ = [
     'DigitalOceanNodeDriver',
     'DigitalOcean_v1_NodeDriver',
-    'DigitalOcean_v1_NodeDriver'
+    'DigitalOcean_v2_NodeDriver'
 ]
 
 
@@ -35,6 +37,15 @@ class DigitalOceanNodeDriver(NodeDriver):
     """
     DigitalOcean NodeDriver defaulting to using APIv2.
 
+    :keyword    key: Required for authentication. Used in both ``v1`` and
+                     ``v2`` implementations.
+    :type       key: ``str``
+
+    :keyword    secret: Used in driver authentication with key. Defaults to
+                        None and when set, will cause driver to use ``v1`` for
+                        connection and response. (optional)
+    :type       secret: ``str``
+
     :keyword    api_version: Specifies the API version to use. ``v1`` and
                              ``v2`` are the only valid options. Defaults to
                              using ``v2`` (optional)
@@ -46,7 +57,7 @@ class DigitalOceanNodeDriver(NodeDriver):
 
     def __new__(cls, key, secret=None, api_version='v2', **kwargs):
         if cls is DigitalOceanNodeDriver:
-            if api_version == 'v1':
+            if api_version == 'v1' or secret != None:
                 cls = DigitalOcean_v1_NodeDriver
             elif api_version == 'v2':
                 cls = DigitalOcean_v2_NodeDriver
@@ -55,45 +66,7 @@ class DigitalOceanNodeDriver(NodeDriver):
                                           (api_version))
         return super(DigitalOceanNodeDriver, cls).__new__(cls, **kwargs)
 
-
-class DigitalOcean_v1_Response(JsonResponse):
-    def parse_error(self):
-        if self.status == httplib.FOUND and '/api/error' in self.body:
-            # Hacky, but DigitalOcean error responses are awful
-            raise InvalidCredsError(self.body)
-        elif self.status == httplib.UNAUTHORIZED:
-            body = self.parse_body()
-            raise InvalidCredsError(body['message'])
-        else:
-            body = self.parse_body()
-
-            if 'error_message' in body:
-                error = '%s (code: %s)' % (body['error_message'], self.status)
-            else:
-                error = body
-            return error
-
-
-class DigitalOcean_v2_Response(JsonResponse):
-    valid_response_codes = [httplib.OK, httplib.ACCEPTED, httplib.CREATED,
-                            httplib.NO_CONTENT]
-
-    def parse_error(self):
-        if self.status == httplib.UNAUTHORIZED:
-            body = self.parse_body()
-            raise InvalidCredsError(body['message'])
-        else:
-            body = self.parse_body()
-            if 'message' in body:
-                error = '%s (code: %s)' % (body['message'], self.status)
-            else:
-                error = body
-            return error
-
-    def success(self):
-        return self.status in self.valid_response_codes
-
-
+# TODO Implement v1 driver using KeyPair
 class SSHKey(object):
     def __init__(self, id, name, pub_key):
         self.id = id
@@ -105,52 +78,11 @@ class SSHKey(object):
                 (self.id, self.name, self.pub_key))
 
 
-class DigitalOcean_v1_Connection(ConnectionUserAndKey):
-    """
-    Connection class for the DigitalOcean (v1) driver.
-    """
-
-    host = 'api.digitalocean.com'
-    responseCls = DigitalOcean_v1_Response
-
-    def add_default_params(self, params):
-        """
-        Add parameters that are necessary for every request
-
-        This method adds ``client_id`` and ``api_key`` to
-        the request.
-        """
-        params['client_id'] = self.user_id
-        params['api_key'] = self.key
-        return params
-
-
-class DigitalOcean_v2_Connection(ConnectionKey):
-    """
-    Connection class for the DigitalOcean (v2) driver.
-    """
-
-    host = 'api.digitalocean.com'
-    responseCls = DigitalOcean_v2_Response
-
-    def add_default_headers(self, headers):
-        """
-        Add headers that are necessary for every request
-
-        This method adds ``token`` to the request.
-        """
-        headers['Authorization'] = 'Bearer %s' % (self.key)
-        headers['Content-Type'] = 'application/json'
-        return headers
-
-
-class DigitalOcean_v1_NodeDriver(DigitalOceanNodeDriver):
+class DigitalOcean_v1_NodeDriver(DigitalOcean_v1_BaseDriver, DigitalOceanNodeDriver):
     """
     DigitalOcean NodeDriver using v1 of the API.
     """
 
-    connectionCls = DigitalOcean_v1_Connection
-
     NODE_STATE_MAP = {'new': NodeState.PENDING,
                       'off': NodeState.REBOOTING,
                       'active': NodeState.RUNNING}
@@ -215,17 +147,33 @@ class DigitalOcean_v1_NodeDriver(DigitalOceanNodeDriver):
                                       params=params)
         return res.status == httplib.OK
 
-    def ex_list_ssh_keys(self):
+    def list_key_pairs(self):
         """
         List all the available SSH keys.
 
         :return: Available SSH keys.
-        :rtype: ``list`` of :class:`SSHKey`
+        :rtype: ``list`` of :class:`KeyPair`
         """
         data = self.connection.request('/v1/ssh_keys').object['ssh_keys']
-        return list(map(self._to_ssh_key, data))
+        return list(map(self._to_key_pair, data))
+
+    def get_key_pair(self, name):
+        """
+        Retrieve a single key pair.
+
+        :param name: Name of the key pair to retrieve.
+        :type name: ``str``
 
-    def ex_create_ssh_key(self, name, ssh_key_pub):
+        :rtype: :class:`.KeyPair`
+        """
+        qkey = [k for k in self.list_key_pairs() if k.name == name][0]
+        data = self.connection.request('/v1/ssh_keys/%s' %
+                                       qkey.extra['id']).object['ssh_key']
+        return self._to_key_pair(data=data)
+
+    #TODO: This adds the ssh_key_pub parameter. This puts the burden of making
+    #      it within the function or on the API. The KeyPair API needs work.
+    def create_key_pair(self, name, ssh_key_pub):
         """
         Create a new SSH key.
 
@@ -239,16 +187,20 @@ class DigitalOcean_v1_NodeDriver(DigitalOceanNodeDriver):
         data = self.connection.request('/v1/ssh_keys/new/', method='GET',
                                        params=params).object
         assert 'ssh_key' in data
-        return self._to_ssh_key(data=data['ssh_key'])
+        #TODO: libcloud.compute.base.KeyPair.create_key_pair doesn't specify
+        #      a return value. This looks like it should return a KeyPair
+        return self._to_key_pair(data=data['ssh_key'])
 
-    def ex_destroy_ssh_key(self, key_id):
+    def delete_key_pair(self, key_pair):
         """
-        Delete an existing SSH key.
+        Delete an existing key pair.
 
-        :param      key_id: SSH key id (required)
-        :type       key_id: ``str``
+        :param key_pair: Key pair object.
+        :type key_pair: :class:`.KeyPair`
         """
-        res = self.connection.request('/v1/ssh_keys/%s/destroy/' % (key_id))
+        res = self.connection.request('/v1/ssh_keys/%s/destroy/' %
+                                      key_pair.extra['id'])
+        #TODO: This looks like it should return bool like the other delete_*
         return res.status == httplib.OK
 
     def _to_node(self, data):
@@ -293,18 +245,24 @@ class DigitalOcean_v1_NodeDriver(DigitalOceanNodeDriver):
         return NodeSize(id=data['id'], name=data['name'], ram=ram, disk=0,
                         bandwidth=0, price=0, driver=self)
 
+    def _to_key_pair(self, data):
+        try:
+            pubkey = data['ssh_pub_key']
+        except KeyError:
+            pubkey = None
+        return KeyPair(data['name'], public_key=pubkey, fingerprint=None,
+                       driver=self, private_key=None, extra={'id':data['id']})
+
     def _to_ssh_key(self, data):
         return SSHKey(id=data['id'], name=data['name'],
                       pub_key=data.get('ssh_pub_key', None))
 
 
-class DigitalOcean_v2_NodeDriver(DigitalOceanNodeDriver):
+class DigitalOcean_v2_NodeDriver(DigitalOcean_v2_BaseDriver, DigitalOceanNodeDriver):
     """
     DigitalOcean NodeDriver using v2 of the API.
     """
 
-    connectionCls = DigitalOcean_v2_Connection
-
     NODE_STATE_MAP = {'new': NodeState.PENDING,
                       'off': NodeState.STOPPED,
                       'active': NodeState.RUNNING,
@@ -440,8 +398,22 @@ class DigitalOcean_v2_NodeDriver(DigitalOceanNodeDriver):
         :return: Available SSH keys.
         :rtype: ``list`` of :class:`KeyPair`
         """
-        data = self.connection.request('/v2/account/keys').object['ssh_keys']
-        return list(map(self._to_key_pairs, data))
+        data = self._paginated_request('/v2/account/keys', 'ssh_keys')
+        return list(map(self._to_key_pair, data))
+
+    def get_key_pair(self, name):
+        """
+        Retrieve a single key pair.
+
+        :param name: Name of the key pair to retrieve.
+        :type name: ``str``
+
+        :rtype: :class:`.KeyPair`
+        """
+        qkey = [k for k in self.list_key_pairs() if k.name == name][0]
+        data = self.connection.request('/v2/account/keys/%s' %
+                                       qkey.extra['id']).object['ssh_key']
+        return self._to_key_pair(data=data)
 
     def create_key_pair(self, name, public_key):
         """
@@ -456,7 +428,7 @@ class DigitalOcean_v2_NodeDriver(DigitalOceanNodeDriver):
         params = {'name': name, 'public_key': public_key}
         data = self.connection.request('/v2/account/keys', method='POST',
                                        params=params).object['ssh_key']
-        return self._to_key_pairs(data=data)
+        return self._to_key_pair(data=data)
 
     def delete_key_pair(self, key):
         """
@@ -543,7 +515,7 @@ class DigitalOcean_v2_NodeDriver(DigitalOceanNodeDriver):
                         disk=data['disk'], bandwidth=data['transfer'],
                         price=data['price_hourly'], driver=self, extra=extra)
 
-    def _to_key_pairs(self, data):
+    def _to_key_pair(self, data):
         extra = {'id': data['id']}
         return KeyPair(name=data['name'],
                        fingerprint=data['fingerprint'],

http://git-wip-us.apache.org/repos/asf/libcloud/blob/9224e8e1/libcloud/test/compute/test_digitalocean_v1.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_digitalocean_v1.py b/libcloud/test/compute/test_digitalocean_v1.py
index 030e2ee..c8ff999 100644
--- a/libcloud/test/compute/test_digitalocean_v1.py
+++ b/libcloud/test/compute/test_digitalocean_v1.py
@@ -110,17 +110,17 @@ class DigitalOcean_v1_Tests(LibcloudTestCase):
         result = self.driver.ex_rename_node(node, 'fedora helios')
         self.assertTrue(result)
 
-    def test_ex_list_ssh_keys(self):
-        keys = self.driver.ex_list_ssh_keys()
+    def test_list_key_pairs(self):
+        keys = self.driver.list_key_pairs()
         self.assertEqual(len(keys), 1)
 
-        self.assertEqual(keys[0].id, 7717)
+        self.assertEqual(keys[0].extra['id'], 7717)
         self.assertEqual(keys[0].name, 'test1')
-        self.assertEqual(keys[0].pub_key, None)
+        self.assertEqual(keys[0].public_key, None)
 
-    def test_ex_destroy_ssh_key(self):
-        key = self.driver.ex_list_ssh_keys()[0]
-        result = self.driver.ex_destroy_ssh_key(key.id)
+    def test_delete_key_pair(self):
+        key = self.driver.list_key_pairs()[0]
+        result = self.driver.delete_key_pair(key)
         self.assertTrue(result)