libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject [2/5] libcloud git commit: Add support for DigitalOcean's APIv2 and use it by default while maintaining support for v1.
Date Sun, 29 Mar 2015 15:27:49 GMT
Add support for DigitalOcean's APIv2 and use it by default while maintaining
support for v1.

Closes #443

Signed-off-by: Tomaz Muraus <tomaz@apache.org>


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

Branch: refs/heads/trunk
Commit: 92fa28608843eab9277d0434263b5e4d89224eb8
Parents: c218088
Author: Andrew Starr-Bochicchio <a.starr.b@gmail.com>
Authored: Sun Feb 1 13:14:08 2015 -0500
Committer: Tomaz Muraus <tomaz@apache.org>
Committed: Sun Mar 29 13:57:45 2015 +0200

----------------------------------------------------------------------
 CHANGES.rst                                     |   8 +
 libcloud/compute/drivers/digitalocean.py        | 365 +++++++++++++++++--
 .../fixtures/digitalocean/create_image.json     |  12 +
 .../fixtures/digitalocean/create_key_pair.json  |   8 +
 .../fixtures/digitalocean/create_node.json      |   1 -
 .../fixtures/digitalocean/destroy_node.json     |   1 -
 .../compute/fixtures/digitalocean/error.json    |   1 +
 .../compute/fixtures/digitalocean/error.txt     |   1 -
 .../digitalocean/error_invalid_image.json       |   1 -
 .../digitalocean/ex_create_ssh_key.json         |   1 -
 .../digitalocean/ex_destroy_ssh_key.json        |   1 -
 .../fixtures/digitalocean/ex_list_ssh_keys.json |   1 -
 .../fixtures/digitalocean/ex_power_on_node.json |  12 +
 .../fixtures/digitalocean/ex_rename_node.json   |   1 -
 .../fixtures/digitalocean/ex_shutdown_node.json |  12 +
 .../fixtures/digitalocean/get_image.json        |  14 +
 .../fixtures/digitalocean/list_images.json      | 145 --------
 .../fixtures/digitalocean/list_key_pairs.json   |  13 +
 .../fixtures/digitalocean/list_locations.json   |   1 -
 .../fixtures/digitalocean/list_nodes.json       |   1 -
 .../fixtures/digitalocean/list_nodes_empty.json |   1 -
 .../digitalocean/list_nodes_page_1.json         |  95 +++++
 .../fixtures/digitalocean/list_sizes.json       |   1 -
 .../fixtures/digitalocean/reboot_node.json      |   1 -
 .../fixtures/digitalocean_v1/create_node.json   |   1 +
 .../fixtures/digitalocean_v1/destroy_node.json  |   1 +
 .../compute/fixtures/digitalocean_v1/error.txt  |   1 +
 .../digitalocean_v1/error_invalid_image.json    |   1 +
 .../digitalocean_v1/ex_create_ssh_key.json      |   1 +
 .../digitalocean_v1/ex_destroy_ssh_key.json     |   1 +
 .../digitalocean_v1/ex_list_ssh_keys.json       |   1 +
 .../digitalocean_v1/ex_rename_node.json         |   1 +
 .../fixtures/digitalocean_v1/list_images.json   | 145 ++++++++
 .../digitalocean_v1/list_locations.json         |   1 +
 .../fixtures/digitalocean_v1/list_nodes.json    |   1 +
 .../digitalocean_v1/list_nodes_empty.json       |   1 +
 .../digitalocean_v1/list_nodes_page_2.json      |  95 +++++
 .../fixtures/digitalocean_v1/list_sizes.json    |   1 +
 .../fixtures/digitalocean_v1/reboot_node.json   |   1 +
 .../fixtures/digitalocean_v2/create_image.json  |  12 +
 .../digitalocean_v2/create_key_pair.json        |   8 +
 .../fixtures/digitalocean_v2/create_node.json   |  13 +
 .../compute/fixtures/digitalocean_v2/error.json |   1 +
 .../digitalocean_v2/error_invalid_image.json    |   1 +
 .../digitalocean_v2/ex_power_on_node.json       |  12 +
 .../digitalocean_v2/ex_rename_node.json         |  13 +
 .../digitalocean_v2/ex_shutdown_node.json       |  12 +
 .../fixtures/digitalocean_v2/get_image.json     |  14 +
 .../fixtures/digitalocean_v2/list_images.json   |  44 +++
 .../digitalocean_v2/list_key_pairs.json         |  13 +
 .../digitalocean_v2/list_locations.json         |  48 +++
 .../fixtures/digitalocean_v2/list_nodes.json    |  89 +++++
 .../digitalocean_v2/list_nodes_empty.json       |   7 +
 .../digitalocean_v2/list_nodes_page_1.json      |  95 +++++
 .../fixtures/digitalocean_v2/list_sizes.json    |  35 ++
 .../fixtures/digitalocean_v2/reboot_node.json   |  13 +
 libcloud/test/compute/test_digitalocean.py      | 181 ---------
 libcloud/test/compute/test_digitalocean_v1.py   | 182 +++++++++
 libcloud/test/compute/test_digitalocean_v2.py   | 268 ++++++++++++++
 libcloud/test/secrets.py-dist                   |   3 +-
 60 files changed, 1650 insertions(+), 365 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index a4185bf..c5d4972 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -129,6 +129,14 @@ Compute
   (LIBCLOUD-670, GITHUB-472)
   [Dinesh Bhoopathy]
 
+- Add support for DigitalOcean API v2.0 while maintaining support for the old
+  API v2.0.
+
+  Note: API v2.0 is now used by default. To use the old API v1.0, pass
+  ``api_version='1.0'`` argument to the driver constructor.
+  (GITHUB-442)
+  [Andrew Starr-Bochicchio]
+
 DNS
 ~~~
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/compute/drivers/digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/digitalocean.py b/libcloud/compute/drivers/digitalocean.py
index 4ec6da1..5daf0e8 100644
--- a/libcloud/compute/drivers/digitalocean.py
+++ b/libcloud/compute/drivers/digitalocean.py
@@ -18,13 +18,45 @@ Digital Ocean Driver
 
 from libcloud.utils.py3 import httplib
 
-from libcloud.common.base import ConnectionUserAndKey, JsonResponse
+from libcloud.common.base import ConnectionUserAndKey, ConnectionKey
+from libcloud.common.base import JsonResponse
 from libcloud.compute.types import Provider, NodeState, InvalidCredsError
-from libcloud.compute.base import NodeDriver
-from libcloud.compute.base import Node, NodeImage, NodeSize, NodeLocation
+from libcloud.compute.base import NodeDriver, Node
+from libcloud.compute.base import NodeImage, NodeSize, NodeLocation, KeyPair
 
+__all__ = [
+    'DigitalOceanNodeDriver',
+    'DigitalOcean_v1_NodeDriver',
+    'DigitalOcean_v1_NodeDriver'
+]
 
-class DigitalOceanResponse(JsonResponse):
+
+class DigitalOceanNodeDriver(NodeDriver):
+    """
+    DigitalOcean NodeDriver defaulting to using APIv2.
+
+    :keyword    api_version: Specifies the API version to use. ``v1`` and
+                             ``v2`` are the only valid options. Defaults to
+                             using ``v2`` (optional)
+    :type       api_version: ``str``
+    """
+    type = Provider.DIGITAL_OCEAN
+    name = 'DigitalOcean'
+    website = 'https://www.digitalocean.com'
+
+    def __new__(cls, key, secret=None, api_version='v2', **kwargs):
+        if cls is DigitalOceanNodeDriver:
+            if api_version == 'v1':
+                cls = DigitalOcean_v1_NodeDriver
+            elif api_version == 'v2':
+                cls = DigitalOcean_v2_NodeDriver
+            else:
+                raise NotImplementedError('Unsupported API version: %s' %
+                                          (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
@@ -42,6 +74,26 @@ class DigitalOceanResponse(JsonResponse):
             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
+
+
 class SSHKey(object):
     def __init__(self, id, name, pub_key):
         self.id = id
@@ -53,13 +105,13 @@ class SSHKey(object):
                 (self.id, self.name, self.pub_key))
 
 
-class DigitalOceanConnection(ConnectionUserAndKey):
+class DigitalOcean_v1_Connection(ConnectionUserAndKey):
     """
-    Connection class for the DigitalOcean driver.
+    Connection class for the DigitalOcean (v1) driver.
     """
 
     host = 'api.digitalocean.com'
-    responseCls = DigitalOceanResponse
+    responseCls = DigitalOcean_v1_Response
 
     def add_default_params(self, params):
         """
@@ -73,35 +125,50 @@ class DigitalOceanConnection(ConnectionUserAndKey):
         return params
 
 
-class DigitalOceanNodeDriver(NodeDriver):
+class DigitalOcean_v2_Connection(ConnectionKey):
     """
-    DigitalOceanNode node driver.
+    Connection class for the DigitalOcean (v2) driver.
     """
 
-    connectionCls = DigitalOceanConnection
+    host = 'api.digitalocean.com'
+    responseCls = DigitalOcean_v2_Response
 
-    type = Provider.DIGITAL_OCEAN
-    name = 'Digital Ocean'
-    website = 'https://www.digitalocean.com'
+    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):
+    """
+    DigitalOcean NodeDriver using v1 of the API.
+    """
+
+    connectionCls = DigitalOcean_v1_Connection
 
     NODE_STATE_MAP = {'new': NodeState.PENDING,
                       'off': NodeState.REBOOTING,
                       'active': NodeState.RUNNING}
 
     def list_nodes(self):
-        data = self.connection.request('/droplets').object['droplets']
+        data = self.connection.request('/v1/droplets').object['droplets']
         return list(map(self._to_node, data))
 
     def list_locations(self):
-        data = self.connection.request('/regions').object['regions']
+        data = self.connection.request('/v1/regions').object['regions']
         return list(map(self._to_location, data))
 
     def list_images(self):
-        data = self.connection.request('/images').object['images']
+        data = self.connection.request('/v1/images').object['images']
         return list(map(self._to_image, data))
 
     def list_sizes(self):
-        data = self.connection.request('/sizes').object['sizes']
+        data = self.connection.request('/v1/sizes').object['sizes']
         return list(map(self._to_size, data))
 
     def create_node(self, name, size, image, location, ex_ssh_key_ids=None,
@@ -122,22 +189,22 @@ class DigitalOceanNodeDriver(NodeDriver):
         if ex_ssh_key_ids:
             params['ssh_key_ids'] = ','.join(ex_ssh_key_ids)
 
-        data = self.connection.request('/droplets/new', params=params).object
-        return self._to_node(data=data['droplet'])
+        data = self.connection.request('/v1/droplets/new', params=params)
+        return self._to_node(data=data.object['droplet'])
 
     def reboot_node(self, node):
-        res = self.connection.request('/droplets/%s/reboot/' % (node.id))
+        res = self.connection.request('/v1/droplets/%s/reboot/' % (node.id))
         return res.status == httplib.OK
 
     def destroy_node(self, node):
         params = {'scrub_data': '1'}
-        res = self.connection.request('/droplets/%s/destroy/' % (node.id),
+        res = self.connection.request('/v1/droplets/%s/destroy/' % (node.id),
                                       params=params)
         return res.status == httplib.OK
 
     def ex_rename_node(self, node, name):
         params = {'name': name}
-        res = self.connection.request('/droplets/%s/rename/' % (node.id),
+        res = self.connection.request('/v1/droplets/%s/rename/' % (node.id),
                                       params=params)
         return res.status == httplib.OK
 
@@ -148,7 +215,7 @@ class DigitalOceanNodeDriver(NodeDriver):
         :return: Available SSH keys.
         :rtype: ``list`` of :class:`SSHKey`
         """
-        data = self.connection.request('/ssh_keys').object['ssh_keys']
+        data = self.connection.request('/v1/ssh_keys').object['ssh_keys']
         return list(map(self._to_ssh_key, data))
 
     def ex_create_ssh_key(self, name, ssh_key_pub):
@@ -162,7 +229,7 @@ class DigitalOceanNodeDriver(NodeDriver):
         :type       name: ``str``
         """
         params = {'name': name, 'ssh_pub_key': ssh_key_pub}
-        data = self.connection.request('/ssh_keys/new/', method='GET',
+        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'])
@@ -174,7 +241,7 @@ class DigitalOceanNodeDriver(NodeDriver):
         :param      key_id: SSH key id (required)
         :type       key_id: ``str``
         """
-        res = self.connection.request('/ssh_keys/%s/destroy/' % (key_id))
+        res = self.connection.request('/v1/ssh_keys/%s/destroy/' % (key_id))
         return res.status == httplib.OK
 
     def _to_node(self, data):
@@ -222,3 +289,251 @@ class DigitalOceanNodeDriver(NodeDriver):
     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):
+    """
+    DigitalOcean NodeDriver using v2 of the API.
+    """
+
+    connectionCls = DigitalOcean_v2_Connection
+
+    NODE_STATE_MAP = {'new': NodeState.PENDING,
+                      'off': NodeState.STOPPED,
+                      'active': NodeState.RUNNING,
+                      'archive': NodeState.TERMINATED}
+
+    def list_nodes(self):
+        data = self._paginated_request('/v2/droplets', 'droplets')
+        return list(map(self._to_node, data))
+
+    def list_locations(self):
+        data = self.connection.request('/v2/regions').object['regions']
+        return list(map(self._to_location, data))
+
+    def list_images(self):
+        data = self._paginated_request('/v2/images', 'images')
+        return list(map(self._to_image, data))
+
+    def list_sizes(self):
+        data = self.connection.request('/v2/sizes').object['sizes']
+        return list(map(self._to_size, data))
+
+    def create_node(self, name, size, image, location, ex_ssh_key_ids=None,
+                    **kwargs):
+        """
+        Create a node.
+
+        :keyword    ex_ssh_key_ids: A list of ssh key ids which will be added
+                                   to the server. (optional)
+        :type       ex_ssh_key_ids: ``list`` of ``str``
+
+        :return: The newly created node.
+        :rtype: :class:`Node`
+        """
+        params = {'name': name, 'size': size.name, 'image': image.id,
+                  'region': location.id}
+
+        if ex_ssh_key_ids:
+            params['ssh_key_ids'] = ','.join(ex_ssh_key_ids)
+
+        data = self.connection.request('/v2/droplets',
+                                       params=params, method='POST').object
+        return self._to_node(data=data['droplet'])
+
+    def reboot_node(self, node):
+        params = {'type': 'reboot'}
+        res = self.connection.request('/v2/droplets/%s/actions' % (node.id),
+                                      params=params, method='POST')
+        return res.status == httplib.CREATED
+
+    def destroy_node(self, node):
+        res = self.connection.request('/v2/droplets/%s' % (node.id),
+                                      method='DELETE')
+        return res.status == httplib.NO_CONTENT
+
+    def get_image(self, image_id):
+        """
+        Get an image based on an image_id
+
+        @inherits: :class:`NodeDriver.get_image`
+
+        :param image_id: Image identifier
+        :type image_id: ``int``
+
+        :return: A NodeImage object
+        :rtype: :class:`NodeImage`
+        """
+        res = self.connection.request('/v2/images/%s' % (image_id))
+        data = res.object['image']
+        return self._to_image(data)
+
+    def create_image(self, node, name):
+        """
+        Create an image fron a Node.
+
+        @inherits: :class:`NodeDriver.create_image`
+
+        :param node: Node to use as base for image
+        :type node: :class:`Node`
+
+        :param node: Name for image
+        :type node: ``str``
+
+        :rtype: ``bool``
+        """
+        params = {'type': 'snapshot', 'name': name}
+        res = self.connection.request('/v2/droplets/%s/actions' % (node.id),
+                                      params=params, method='POST')
+        return res.status == httplib.CREATED
+
+    def delete_image(self, image):
+        """Delete an image for node.
+
+        @inherits: :class:`NodeDriver.delete_image`
+
+        :param      image: the image to be deleted
+        :type       image: :class:`NodeImage`
+
+        :rtype: ``bool``
+        """
+        res = self.connection.request('/v2/images/%s' % (image.id),
+                                      method='DELETE')
+        return res.status == httplib.NO_CONTENT
+
+    def ex_rename_node(self, node, name):
+        params = {'type': 'rename', 'name': name}
+        res = self.connection.request('/v2/droplets/%s/actions' % (node.id),
+                                      params=params, method='POST')
+        return res.status == httplib.CREATED
+
+    def ex_shutdown_node(self, node):
+        params = {'type': 'shutdown'}
+        res = self.connection.request('/v2/droplets/%s/actions' % (node.id),
+                                      params=params, method='POST')
+        return res.status == httplib.CREATED
+
+    def ex_power_on_node(self, node):
+        params = {'type': 'power_on'}
+        res = self.connection.request('/v2/droplets/%s/actions' % (node.id),
+                                      params=params, method='POST')
+        return res.status == httplib.CREATED
+
+    def list_key_pairs(self):
+        """
+        List all the available SSH keys.
+
+        :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))
+
+    def create_key_pair(self, name, public_key):
+        """
+        Create a new SSH key.
+
+        :param      name: Key name (required)
+        :type       name: ``str``
+
+        :param      public_key: Valid public key string (required)
+        :type       public_key: ``str``
+        """
+        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)
+
+    def delete_key_pair(self, key):
+        """
+        Delete an existing SSH key.
+
+        :param      key: SSH key (required)
+        :type       key: :class:`KeyPair`
+        """
+        key_id = key.extra['id']
+        res = self.connection.request('/v2/account/keys/%s' % (key_id),
+                                      method='DELETE')
+        return res.status == httplib.NO_CONTENT
+
+    def _paginated_request(self, url, obj):
+        """
+            Perform multiple calls in order to have a full list of elements
+            when the API are paginated.
+        """
+        params = {}
+        data = self.connection.request(url)
+        try:
+            pages = data.object['links']['pages']['last'].split('=')[-1]
+            values = data.object[obj]
+            for page in range(2, int(pages) + 1):
+                params.update({'page': page})
+                new_data = self.connection.request(url, params=params)
+
+                more_values = new_data.object[obj]
+                for value in more_values:
+                    values.append(value)
+            data = values
+        except KeyError:  # No pages.
+            data = data.object[obj]
+
+        return data
+
+    def _to_node(self, data):
+        extra_keys = ['memory', 'vcpus', 'disk', 'region', 'image',
+                      'size_slug', 'locked', 'created_at', 'networks',
+                      'kernel', 'backup_ids', 'snapshot_ids', 'features']
+        if 'status' in data:
+            state = self.NODE_STATE_MAP.get(data['status'], NodeState.UNKNOWN)
+        else:
+            state = NodeState.UNKNOWN
+
+        networks = data['networks']
+        private_ips = []
+        public_ips = []
+        if networks:
+            for net in networks['v4']:
+                if net['type'] == 'private':
+                    private_ips = [net['ip_address']]
+                if net['type'] == 'public':
+                    public_ips = [net['ip_address']]
+
+        extra = {}
+        for key in extra_keys:
+            if key in data:
+                extra[key] = data[key]
+
+        node = Node(id=data['id'], name=data['name'], state=state,
+                    public_ips=public_ips, private_ips=private_ips,
+                    extra=extra, driver=self)
+        return node
+
+    def _to_image(self, data):
+        extra = {'distribution': data['distribution'],
+                 'public': data['public'],
+                 'slug': data['slug'],
+                 'regions': data['regions'],
+                 'min_disk_size': data['min_disk_size'],
+                 'created_at': data['created_at']}
+        return NodeImage(id=data['id'], name=data['name'], extra=extra,
+                         driver=self)
+
+    def _to_location(self, data):
+        return NodeLocation(id=data['slug'], name=data['name'], country=None,
+                            driver=self)
+
+    def _to_size(self, data):
+        extra = {'vcpus': data['vcpus'],
+                 'regions': data['regions']}
+        return NodeSize(id=data['slug'], name=data['slug'], ram=data['memory'],
+                        disk=data['disk'], bandwidth=data['transfer'],
+                        price=data['price_hourly'], driver=self, extra=extra)
+
+    def _to_key_pairs(self, data):
+        extra = {'id': data['id']}
+        return KeyPair(name=data['name'],
+                       fingerprint=data['fingerprint'],
+                       public_key=data['public_key'],
+                       private_key=None,
+                       driver=self,
+                       extra=extra)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/create_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/create_image.json b/libcloud/test/compute/fixtures/digitalocean/create_image.json
new file mode 100644
index 0000000..69d4fb4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/create_image.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36805022,
+    "status": "in-progress",
+    "type": "snapshot",
+    "started_at": "2014-11-14T16:34:39Z",
+    "completed_at": null,
+    "resource_id": 3164450,
+    "resource_type": "droplet",
+    "region": "nyc3"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/create_key_pair.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/create_key_pair.json b/libcloud/test/compute/fixtures/digitalocean/create_key_pair.json
new file mode 100644
index 0000000..9802bc7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/create_key_pair.json
@@ -0,0 +1,8 @@
+{
+  "ssh_key": {
+      "id": 7717,
+      "fingerprint": "f5:d1:78:ed:28:72:5f:e1:ac:94:fd:1f:e0:a3:48:6d",
+      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQsxRiUKn example",
+      "name": "test1"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/create_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/create_node.json b/libcloud/test/compute/fixtures/digitalocean/create_node.json
deleted file mode 100644
index acddc08..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/create_node.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","droplet":{"id":119461,"name":"test-2","image_id":1601,"size_id":66,"event_id":919341}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/destroy_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/destroy_node.json b/libcloud/test/compute/fixtures/digitalocean/destroy_node.json
deleted file mode 100644
index bae818d..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/destroy_node.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/error.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/error.json b/libcloud/test/compute/fixtures/digitalocean/error.json
new file mode 100644
index 0000000..08e22c2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/error.json
@@ -0,0 +1 @@
+{"id":"","message":"Unable to authenticate you."}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/error.txt
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/error.txt b/libcloud/test/compute/fixtures/digitalocean/error.txt
deleted file mode 100644
index 0e90e51..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/error.txt
+++ /dev/null
@@ -1 +0,0 @@
-<html><body>You are being <a href="https://www.digitalocean.com/api/error">redirected</a>.</body></html>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/error_invalid_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/error_invalid_image.json b/libcloud/test/compute/fixtures/digitalocean/error_invalid_image.json
deleted file mode 100644
index e1ccb4f..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/error_invalid_image.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"ERROR","error_message":"You specified an invalid image for Droplet creation.","message":"You specified an invalid image for Droplet creation."}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json b/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json
deleted file mode 100644
index 029cab2..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/ex_create_ssh_key.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","ssh_key":{"id":7717,"name":"test1","ssh_pub_key":"aaq"}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json b/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json
deleted file mode 100644
index 12d1b59..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/ex_destroy_ssh_key.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json b/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json
deleted file mode 100644
index 1111976..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/ex_list_ssh_keys.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","ssh_keys":[{"id":7717,"name":"test1"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_power_on_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_power_on_node.json b/libcloud/test/compute/fixtures/digitalocean/ex_power_on_node.json
new file mode 100644
index 0000000..d7ce8a6
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/ex_power_on_node.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36804758,
+    "status": "in-progress",
+    "type": "power_on",
+    "started_at": "2014-11-14T16:31:19Z",
+    "completed_at": null,
+    "resource_id": 3164450,
+    "resource_type": "droplet",
+    "region": "nyc3"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_rename_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_rename_node.json b/libcloud/test/compute/fixtures/digitalocean/ex_rename_node.json
deleted file mode 100644
index bae818d..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/ex_rename_node.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/ex_shutdown_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/ex_shutdown_node.json b/libcloud/test/compute/fixtures/digitalocean/ex_shutdown_node.json
new file mode 100644
index 0000000..f3507be
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/ex_shutdown_node.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36077293,
+    "status": "in-progress",
+    "type": "shutdown",
+    "started_at": "2014-11-04T17:08:03Z",
+    "completed_at": null,
+    "resource_id": 3067649,
+    "resource_type": "droplet",
+    "region": "nyc2"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/get_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/get_image.json b/libcloud/test/compute/fixtures/digitalocean/get_image.json
new file mode 100644
index 0000000..225e5f3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/get_image.json
@@ -0,0 +1,14 @@
+{
+  "image": {
+    "id": 12345,
+    "name": "My snapshot",
+    "distribution": "Ubuntu",
+    "slug": null,
+    "public": false,
+    "regions": [
+      "nyc2"
+    ],
+    "created_at": "2014-11-04T22:23:02Z",
+    "min_disk_size": 20
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_images.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_images.json b/libcloud/test/compute/fixtures/digitalocean/list_images.json
deleted file mode 100644
index 007a57d..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/list_images.json
+++ /dev/null
@@ -1,145 +0,0 @@
-{
-    "status": "OK",
-    "images": [
-        {
-            "id": 1601,
-            "name": "CentOS 5.8 x64",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 1602,
-            "name": "CentOS 5.8 x32",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 1605,
-            "name": "CentOS 6.0 x32",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 1606,
-            "name": "Fedora 15 x64",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 1609,
-            "name": "Ubuntu 11.10 x32 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 1611,
-            "name": "CentOS 6.2 x64",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 1615,
-            "name": "Fedora 16 x64 Server",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 1618,
-            "name": "Fedora 16 x64 Desktop",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 2676,
-            "name": "Ubuntu 12.04 x64 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 12573,
-            "name": "Debian 6.0 x64",
-            "distribution": "Debian"
-        },
-        {
-            "id": 12574,
-            "name": "CentOS 6.3 x64",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 12575,
-            "name": "Debian 6.0 x32",
-            "distribution": "Debian"
-        },
-        {
-            "id": 12578,
-            "name": "CentOS 6.3 x32",
-            "distribution": "CentOS"
-        },
-        {
-            "id": 14097,
-            "name": "Ubuntu 10.04 x64 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 14098,
-            "name": "Ubuntu 10.04 x32 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 14218,
-            "name": "Ubuntu 12.04 x64 Desktop",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 25306,
-            "name": "Ubuntu 12.10 x32 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 25485,
-            "name": "Ubuntu 12.10 x32 Desktop",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 25489,
-            "name": "Ubuntu 12.10 x64 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 25493,
-            "name": "Ubuntu 12.10 x64 Desktop",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 32387,
-            "name": "Fedora 17 x32 Server",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 32399,
-            "name": "Fedora 17 x32 Desktop",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 32419,
-            "name": "Fedora 17 x64 Desktop",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 32428,
-            "name": "Fedora 17 x64 Server",
-            "distribution": "Fedora"
-        },
-        {
-            "id": 42735,
-            "name": "Ubuntu 12.04 x32 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 43458,
-            "name": "Ubuntu 11.04x64 Server",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 43462,
-            "name": "Ubuntu 11.04x32 Desktop",
-            "distribution": "Ubuntu"
-        },
-        {
-            "id": 46964,
-            "name": "LAMP on Ubuntu 12.04",
-            "distribution": "Ubuntu"
-        }
-    ]
-}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_key_pairs.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_key_pairs.json b/libcloud/test/compute/fixtures/digitalocean/list_key_pairs.json
new file mode 100644
index 0000000..ca155ed
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/list_key_pairs.json
@@ -0,0 +1,13 @@
+{
+  "ssh_keys": [
+    {
+      "id": 7717,
+      "fingerprint": "f5:d1:78:ed:28:72:5f:e1:ac:94:fd:1f:e0:a3:48:6d",
+      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDGk5 example",
+      "name": "test1"
+    }
+  ],
+  "meta": {
+    "total": 1
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_locations.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_locations.json b/libcloud/test/compute/fixtures/digitalocean/list_locations.json
deleted file mode 100644
index a87b38f..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/list_locations.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","regions":[{"id":1,"name":"New York 1"},{"id":2,"name":"Amsterdam 1"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_nodes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_nodes.json b/libcloud/test/compute/fixtures/digitalocean/list_nodes.json
deleted file mode 100644
index 5fe5949..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/list_nodes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","droplets":[{"id":119461,"name":"test-2","image_id":1601,"size_id":66,"region_id":1,"backups_active":null,"ip_address":null,"status":"new"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json b/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json
deleted file mode 100644
index bc62108..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/list_nodes_empty.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","droplets":[]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_nodes_page_1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_nodes_page_1.json b/libcloud/test/compute/fixtures/digitalocean/list_nodes_page_1.json
new file mode 100644
index 0000000..fb6fb08
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean/list_nodes_page_1.json
@@ -0,0 +1,95 @@
+{
+  "droplets": [
+    {
+      "id": 3164444,
+      "name": "example.com",
+      "memory": 512,
+      "vcpus": 1,
+      "disk": 20,
+      "locked": false,
+      "status": "active",
+      "kernel": {
+        "id": 2233,
+        "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
+        "version": "3.13.0-37-generic"
+      },
+      "created_at": "2014-11-14T16:29:21Z",
+      "features": [
+        "backups",
+        "ipv6",
+        "virtio"
+      ],
+      "backup_ids": [
+        7938002
+      ],
+      "snapshot_ids": [
+
+      ],
+      "image": {
+        "id": 6918990,
+        "name": "14.04 x64",
+        "distribution": "Ubuntu",
+        "slug": "ubuntu-14-04-x64",
+        "public": true,
+        "regions": [
+          "nyc1",
+          "ams1",
+          "sfo1",
+          "nyc2",
+          "ams2",
+          "sgp1",
+          "lon1",
+          "nyc3",
+          "ams3",
+          "nyc3"
+        ],
+        "created_at": "2014-10-17T20:24:33Z",
+        "min_disk_size": 20
+      },
+      "size_slug": "512mb",
+      "networks": {
+        "v4": [
+          {
+            "ip_address": "104.236.32.182",
+            "netmask": "255.255.192.0",
+            "gateway": "104.236.0.1",
+            "type": "public"
+          }
+        ],
+        "v6": [
+          {
+            "ip_address": "2604:A880:0800:0010:0000:0000:02DD:4001",
+            "netmask": 64,
+            "gateway": "2604:A880:0800:0010:0000:0000:0000:0001",
+            "type": "public"
+          }
+        ]
+      },
+      "region": {
+        "name": "New York 3",
+        "slug": "nyc3",
+        "sizes": [
+
+        ],
+        "features": [
+          "virtio",
+          "private_networking",
+          "backups",
+          "ipv6",
+          "metadata"
+        ],
+        "available": null
+      }
+    }
+  ],
+  "links": {
+    "pages":
+      {
+        "last":"https://api.digitalocean.com/v2/droplets?page=2",
+        "next":"https://api.digitalocean.com/v2/droplets?page=2"
+      }
+  },
+  "meta": {
+    "total":2
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/list_sizes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/list_sizes.json b/libcloud/test/compute/fixtures/digitalocean/list_sizes.json
deleted file mode 100644
index 99acc57..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/list_sizes.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","sizes":[{"id":66,"name":"512MB"},{"id":63,"name":"1GB"},{"id":62,"name":"2GB"},{"id":64,"name":"4GB"},{"id":65,"name":"8GB"},{"id":61,"name":"16GB"},{"id":60,"name":"32GB"},{"id":70,"name":"48GB"},{"id":69,"name":"64GB"},{"id":68,"name":"96GB"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean/reboot_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean/reboot_node.json b/libcloud/test/compute/fixtures/digitalocean/reboot_node.json
deleted file mode 100644
index bae818d..0000000
--- a/libcloud/test/compute/fixtures/digitalocean/reboot_node.json
+++ /dev/null
@@ -1 +0,0 @@
-{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/create_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/create_node.json b/libcloud/test/compute/fixtures/digitalocean_v1/create_node.json
new file mode 100644
index 0000000..acddc08
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/create_node.json
@@ -0,0 +1 @@
+{"status":"OK","droplet":{"id":119461,"name":"test-2","image_id":1601,"size_id":66,"event_id":919341}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/destroy_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/destroy_node.json b/libcloud/test/compute/fixtures/digitalocean_v1/destroy_node.json
new file mode 100644
index 0000000..bae818d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/destroy_node.json
@@ -0,0 +1 @@
+{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/error.txt
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/error.txt b/libcloud/test/compute/fixtures/digitalocean_v1/error.txt
new file mode 100644
index 0000000..0e90e51
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/error.txt
@@ -0,0 +1 @@
+<html><body>You are being <a href="https://www.digitalocean.com/api/error">redirected</a>.</body></html>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/error_invalid_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/error_invalid_image.json b/libcloud/test/compute/fixtures/digitalocean_v1/error_invalid_image.json
new file mode 100644
index 0000000..e1ccb4f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/error_invalid_image.json
@@ -0,0 +1 @@
+{"status":"ERROR","error_message":"You specified an invalid image for Droplet creation.","message":"You specified an invalid image for Droplet creation."}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/ex_create_ssh_key.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/ex_create_ssh_key.json b/libcloud/test/compute/fixtures/digitalocean_v1/ex_create_ssh_key.json
new file mode 100644
index 0000000..029cab2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/ex_create_ssh_key.json
@@ -0,0 +1 @@
+{"status":"OK","ssh_key":{"id":7717,"name":"test1","ssh_pub_key":"aaq"}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/ex_destroy_ssh_key.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/ex_destroy_ssh_key.json b/libcloud/test/compute/fixtures/digitalocean_v1/ex_destroy_ssh_key.json
new file mode 100644
index 0000000..12d1b59
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/ex_destroy_ssh_key.json
@@ -0,0 +1 @@
+{"status":"OK"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/ex_list_ssh_keys.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/ex_list_ssh_keys.json b/libcloud/test/compute/fixtures/digitalocean_v1/ex_list_ssh_keys.json
new file mode 100644
index 0000000..1111976
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/ex_list_ssh_keys.json
@@ -0,0 +1 @@
+{"status":"OK","ssh_keys":[{"id":7717,"name":"test1"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/ex_rename_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/ex_rename_node.json b/libcloud/test/compute/fixtures/digitalocean_v1/ex_rename_node.json
new file mode 100644
index 0000000..bae818d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/ex_rename_node.json
@@ -0,0 +1 @@
+{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_images.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_images.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_images.json
new file mode 100644
index 0000000..007a57d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_images.json
@@ -0,0 +1,145 @@
+{
+    "status": "OK",
+    "images": [
+        {
+            "id": 1601,
+            "name": "CentOS 5.8 x64",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 1602,
+            "name": "CentOS 5.8 x32",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 1605,
+            "name": "CentOS 6.0 x32",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 1606,
+            "name": "Fedora 15 x64",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 1609,
+            "name": "Ubuntu 11.10 x32 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 1611,
+            "name": "CentOS 6.2 x64",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 1615,
+            "name": "Fedora 16 x64 Server",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 1618,
+            "name": "Fedora 16 x64 Desktop",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 2676,
+            "name": "Ubuntu 12.04 x64 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 12573,
+            "name": "Debian 6.0 x64",
+            "distribution": "Debian"
+        },
+        {
+            "id": 12574,
+            "name": "CentOS 6.3 x64",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 12575,
+            "name": "Debian 6.0 x32",
+            "distribution": "Debian"
+        },
+        {
+            "id": 12578,
+            "name": "CentOS 6.3 x32",
+            "distribution": "CentOS"
+        },
+        {
+            "id": 14097,
+            "name": "Ubuntu 10.04 x64 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 14098,
+            "name": "Ubuntu 10.04 x32 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 14218,
+            "name": "Ubuntu 12.04 x64 Desktop",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 25306,
+            "name": "Ubuntu 12.10 x32 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 25485,
+            "name": "Ubuntu 12.10 x32 Desktop",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 25489,
+            "name": "Ubuntu 12.10 x64 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 25493,
+            "name": "Ubuntu 12.10 x64 Desktop",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 32387,
+            "name": "Fedora 17 x32 Server",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 32399,
+            "name": "Fedora 17 x32 Desktop",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 32419,
+            "name": "Fedora 17 x64 Desktop",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 32428,
+            "name": "Fedora 17 x64 Server",
+            "distribution": "Fedora"
+        },
+        {
+            "id": 42735,
+            "name": "Ubuntu 12.04 x32 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 43458,
+            "name": "Ubuntu 11.04x64 Server",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 43462,
+            "name": "Ubuntu 11.04x32 Desktop",
+            "distribution": "Ubuntu"
+        },
+        {
+            "id": 46964,
+            "name": "LAMP on Ubuntu 12.04",
+            "distribution": "Ubuntu"
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_locations.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_locations.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_locations.json
new file mode 100644
index 0000000..a87b38f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_locations.json
@@ -0,0 +1 @@
+{"status":"OK","regions":[{"id":1,"name":"New York 1"},{"id":2,"name":"Amsterdam 1"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes.json
new file mode 100644
index 0000000..5fe5949
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes.json
@@ -0,0 +1 @@
+{"status":"OK","droplets":[{"id":119461,"name":"test-2","image_id":1601,"size_id":66,"region_id":1,"backups_active":null,"ip_address":null,"status":"new"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_empty.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_empty.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_empty.json
new file mode 100644
index 0000000..bc62108
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_empty.json
@@ -0,0 +1 @@
+{"status":"OK","droplets":[]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_page_2.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_page_2.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_page_2.json
new file mode 100644
index 0000000..e6b89ba
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_nodes_page_2.json
@@ -0,0 +1,95 @@
+{
+  "droplets": [
+    {
+      "id": 3164445,
+      "name": "example1.com",
+      "memory": 512,
+      "vcpus": 1,
+      "disk": 20,
+      "locked": false,
+      "status": "active",
+      "kernel": {
+        "id": 2233,
+        "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
+        "version": "3.13.0-37-generic"
+      },
+      "created_at": "2014-11-14T16:29:21Z",
+      "features": [
+        "backups",
+        "ipv6",
+        "virtio"
+      ],
+      "backup_ids": [
+        7938002
+      ],
+      "snapshot_ids": [
+
+      ],
+      "image": {
+        "id": 6918990,
+        "name": "14.04 x64",
+        "distribution": "Ubuntu",
+        "slug": "ubuntu-14-04-x64",
+        "public": true,
+        "regions": [
+          "nyc1",
+          "ams1",
+          "sfo1",
+          "nyc2",
+          "ams2",
+          "sgp1",
+          "lon1",
+          "nyc3",
+          "ams3",
+          "nyc3"
+        ],
+        "created_at": "2014-10-17T20:24:33Z",
+        "min_disk_size": 20
+      },
+      "size_slug": "512mb",
+      "networks": {
+        "v4": [
+          {
+            "ip_address": "104.236.32.182",
+            "netmask": "255.255.192.0",
+            "gateway": "104.236.0.1",
+            "type": "public"
+          }
+        ],
+        "v6": [
+          {
+            "ip_address": "2604:A880:0800:0010:0000:0000:02DD:4001",
+            "netmask": 64,
+            "gateway": "2604:A880:0800:0010:0000:0000:0000:0001",
+            "type": "public"
+          }
+        ]
+      },
+      "region": {
+        "name": "New York 3",
+        "slug": "nyc3",
+        "sizes": [
+
+        ],
+        "features": [
+          "virtio",
+          "private_networking",
+          "backups",
+          "ipv6",
+          "metadata"
+        ],
+        "available": null
+      }
+    }
+  ],
+  "links": {
+    "pages":
+      {
+        "first":"https://api.digitalocean.com/v2/droplets?page=1",
+        "prev":"https://api.digitalocean.com/v2/droplets?page=1"
+      }
+  },
+  "meta": {
+    "total":2
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/list_sizes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/list_sizes.json b/libcloud/test/compute/fixtures/digitalocean_v1/list_sizes.json
new file mode 100644
index 0000000..99acc57
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/list_sizes.json
@@ -0,0 +1 @@
+{"status":"OK","sizes":[{"id":66,"name":"512MB"},{"id":63,"name":"1GB"},{"id":62,"name":"2GB"},{"id":64,"name":"4GB"},{"id":65,"name":"8GB"},{"id":61,"name":"16GB"},{"id":60,"name":"32GB"},{"id":70,"name":"48GB"},{"id":69,"name":"64GB"},{"id":68,"name":"96GB"}]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v1/reboot_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v1/reboot_node.json b/libcloud/test/compute/fixtures/digitalocean_v1/reboot_node.json
new file mode 100644
index 0000000..bae818d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v1/reboot_node.json
@@ -0,0 +1 @@
+{"status":"OK","event_id":918910}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/create_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/create_image.json b/libcloud/test/compute/fixtures/digitalocean_v2/create_image.json
new file mode 100644
index 0000000..69d4fb4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/create_image.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36805022,
+    "status": "in-progress",
+    "type": "snapshot",
+    "started_at": "2014-11-14T16:34:39Z",
+    "completed_at": null,
+    "resource_id": 3164450,
+    "resource_type": "droplet",
+    "region": "nyc3"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/create_key_pair.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/create_key_pair.json b/libcloud/test/compute/fixtures/digitalocean_v2/create_key_pair.json
new file mode 100644
index 0000000..9802bc7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/create_key_pair.json
@@ -0,0 +1,8 @@
+{
+  "ssh_key": {
+      "id": 7717,
+      "fingerprint": "f5:d1:78:ed:28:72:5f:e1:ac:94:fd:1f:e0:a3:48:6d",
+      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQsxRiUKn example",
+      "name": "test1"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/create_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/create_node.json b/libcloud/test/compute/fixtures/digitalocean_v2/create_node.json
new file mode 100644
index 0000000..8e54e39
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/create_node.json
@@ -0,0 +1,13 @@
+{
+  "action":
+    {
+      "id":39290099,
+      "status":"completed",
+      "type":"create",
+      "started_at":"2014-12-19T19:14:36Z",
+      "completed_at":"2014-12-19T19:15:23Z",
+      "resource_id":12345,
+      "resource_type":"droplet",
+      "region":"nyc3"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/error.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/error.json b/libcloud/test/compute/fixtures/digitalocean_v2/error.json
new file mode 100644
index 0000000..08e22c2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/error.json
@@ -0,0 +1 @@
+{"id":"","message":"Unable to authenticate you."}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/error_invalid_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/error_invalid_image.json b/libcloud/test/compute/fixtures/digitalocean_v2/error_invalid_image.json
new file mode 100644
index 0000000..755cd48
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/error_invalid_image.json
@@ -0,0 +1 @@
+{"id":"unprocessable_entity","message":"You specified an invalid image for Droplet creation."}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/ex_power_on_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/ex_power_on_node.json b/libcloud/test/compute/fixtures/digitalocean_v2/ex_power_on_node.json
new file mode 100644
index 0000000..d7ce8a6
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/ex_power_on_node.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36804758,
+    "status": "in-progress",
+    "type": "power_on",
+    "started_at": "2014-11-14T16:31:19Z",
+    "completed_at": null,
+    "resource_id": 3164450,
+    "resource_type": "droplet",
+    "region": "nyc3"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/ex_rename_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/ex_rename_node.json b/libcloud/test/compute/fixtures/digitalocean_v2/ex_rename_node.json
new file mode 100644
index 0000000..eafe147
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/ex_rename_node.json
@@ -0,0 +1,13 @@
+{
+  "action":
+    {
+      "id":918910,
+      "status":"in-progress",
+      "type":"rename",
+      "started_at":"2014-12-21T02:19:17Z",
+      "completed_at":null,
+      "resource_id":12345,
+      "resource_type":"droplet",
+      "region":"nyc3"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/ex_shutdown_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/ex_shutdown_node.json b/libcloud/test/compute/fixtures/digitalocean_v2/ex_shutdown_node.json
new file mode 100644
index 0000000..f3507be
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/ex_shutdown_node.json
@@ -0,0 +1,12 @@
+{
+  "action": {
+    "id": 36077293,
+    "status": "in-progress",
+    "type": "shutdown",
+    "started_at": "2014-11-04T17:08:03Z",
+    "completed_at": null,
+    "resource_id": 3067649,
+    "resource_type": "droplet",
+    "region": "nyc2"
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/get_image.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/get_image.json b/libcloud/test/compute/fixtures/digitalocean_v2/get_image.json
new file mode 100644
index 0000000..225e5f3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/get_image.json
@@ -0,0 +1,14 @@
+{
+  "image": {
+    "id": 12345,
+    "name": "My snapshot",
+    "distribution": "Ubuntu",
+    "slug": null,
+    "public": false,
+    "regions": [
+      "nyc2"
+    ],
+    "created_at": "2014-11-04T22:23:02Z",
+    "min_disk_size": 20
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_images.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_images.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_images.json
new file mode 100644
index 0000000..a21579b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_images.json
@@ -0,0 +1,44 @@
+{
+  "images": [
+    {
+      "id": 119192817,
+      "name": "14.04 x64",
+      "distribution": "Ubuntu",
+      "slug": "ubuntu-14-04-x64",
+      "public": true,
+      "regions": [
+        "nyc1"
+      ],
+      "created_at": "2014-07-29T14:35:40Z",
+      "min_disk_size": 20
+    },
+    {
+      "id": 449676376,
+      "name": "14.04 x32",
+      "distribution": "Ubuntu",
+      "slug": "ubuntu-14-04-x32",
+      "public": true,
+      "regions": [
+        "nyc1"
+      ],
+      "created_at": "2014-07-29T14:35:40Z",
+      "min_disk_size": 20
+    },
+    {
+      "id": 449676856,
+      "name": "My Snapshot",
+      "distribution": "Ubuntu",
+      "slug": "",
+      "public": false,
+      "regions": [
+        "nyc1",
+        "nyc3"
+      ],
+      "created_at": "2014-08-18T16:35:40Z",
+      "min_disk_size": 40
+    }
+  ],
+  "meta": {
+    "total": 3
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_key_pairs.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_key_pairs.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_key_pairs.json
new file mode 100644
index 0000000..ca155ed
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_key_pairs.json
@@ -0,0 +1,13 @@
+{
+  "ssh_keys": [
+    {
+      "id": 7717,
+      "fingerprint": "f5:d1:78:ed:28:72:5f:e1:ac:94:fd:1f:e0:a3:48:6d",
+      "public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAQQDGk5 example",
+      "name": "test1"
+    }
+  ],
+  "meta": {
+    "total": 1
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_locations.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_locations.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_locations.json
new file mode 100644
index 0000000..50a4831
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_locations.json
@@ -0,0 +1,48 @@
+{
+  "regions": [
+    {
+      "slug": "nyc1",
+      "name": "New York 1",
+      "sizes": [
+        "1gb",
+        "512mb"
+      ],
+      "available": false,
+      "features": [
+        "virtio",
+        "private_networking",
+        "backups",
+        "ipv6"
+      ]
+    },
+    {
+      "slug": "sfo1",
+      "name": "San Francisco 1",
+      "sizes": [
+        "1gb",
+        "512mb"
+      ],
+      "available": true,
+      "features": [
+        "virtio",
+        "backups"
+      ]
+    },
+    {
+      "slug": "ams1",
+      "name": "Amsterdam 1",
+      "sizes": [
+        "1gb",
+        "512mb"
+      ],
+      "available": true,
+      "features": [
+        "virtio",
+        "backups"
+      ]
+    }
+  ],
+  "meta": {
+    "total": 3
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes.json
new file mode 100644
index 0000000..b14ae88
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes.json
@@ -0,0 +1,89 @@
+{
+  "droplets": [
+    {
+      "id": 3164444,
+      "name": "example.com",
+      "memory": 512,
+      "vcpus": 1,
+      "disk": 20,
+      "locked": false,
+      "status": "active",
+      "kernel": {
+        "id": 2233,
+        "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
+        "version": "3.13.0-37-generic"
+      },
+      "created_at": "2014-11-14T16:29:21Z",
+      "features": [
+        "backups",
+        "ipv6",
+        "virtio"
+      ],
+      "backup_ids": [
+        7938002
+      ],
+      "snapshot_ids": [
+
+      ],
+      "image": {
+        "id": 6918990,
+        "name": "14.04 x64",
+        "distribution": "Ubuntu",
+        "slug": "ubuntu-14-04-x64",
+        "public": true,
+        "regions": [
+          "nyc1",
+          "ams1",
+          "sfo1",
+          "nyc2",
+          "ams2",
+          "sgp1",
+          "lon1",
+          "nyc3",
+          "ams3",
+          "nyc3"
+        ],
+        "created_at": "2014-10-17T20:24:33Z",
+        "min_disk_size": 20
+      },
+      "size_slug": "512mb",
+      "networks": {
+        "v4": [
+          {
+            "ip_address": "104.236.32.182",
+            "netmask": "255.255.192.0",
+            "gateway": "104.236.0.1",
+            "type": "public"
+          }
+        ],
+        "v6": [
+          {
+            "ip_address": "2604:A880:0800:0010:0000:0000:02DD:4001",
+            "netmask": 64,
+            "gateway": "2604:A880:0800:0010:0000:0000:0000:0001",
+            "type": "public"
+          }
+        ]
+      },
+      "region": {
+        "name": "New York 3",
+        "slug": "nyc3",
+        "sizes": [
+
+        ],
+        "features": [
+          "virtio",
+          "private_networking",
+          "backups",
+          "ipv6",
+          "metadata"
+        ],
+        "available": null
+      }
+    }
+  ],
+  "links": {},
+  "meta": {
+    "total": 1
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_empty.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_empty.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_empty.json
new file mode 100644
index 0000000..75b843d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_empty.json
@@ -0,0 +1,7 @@
+{
+  "droplets": [],
+  "links": {},
+  "meta": {
+    "total": 0
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_page_1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_page_1.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_page_1.json
new file mode 100644
index 0000000..fb6fb08
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_nodes_page_1.json
@@ -0,0 +1,95 @@
+{
+  "droplets": [
+    {
+      "id": 3164444,
+      "name": "example.com",
+      "memory": 512,
+      "vcpus": 1,
+      "disk": 20,
+      "locked": false,
+      "status": "active",
+      "kernel": {
+        "id": 2233,
+        "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic",
+        "version": "3.13.0-37-generic"
+      },
+      "created_at": "2014-11-14T16:29:21Z",
+      "features": [
+        "backups",
+        "ipv6",
+        "virtio"
+      ],
+      "backup_ids": [
+        7938002
+      ],
+      "snapshot_ids": [
+
+      ],
+      "image": {
+        "id": 6918990,
+        "name": "14.04 x64",
+        "distribution": "Ubuntu",
+        "slug": "ubuntu-14-04-x64",
+        "public": true,
+        "regions": [
+          "nyc1",
+          "ams1",
+          "sfo1",
+          "nyc2",
+          "ams2",
+          "sgp1",
+          "lon1",
+          "nyc3",
+          "ams3",
+          "nyc3"
+        ],
+        "created_at": "2014-10-17T20:24:33Z",
+        "min_disk_size": 20
+      },
+      "size_slug": "512mb",
+      "networks": {
+        "v4": [
+          {
+            "ip_address": "104.236.32.182",
+            "netmask": "255.255.192.0",
+            "gateway": "104.236.0.1",
+            "type": "public"
+          }
+        ],
+        "v6": [
+          {
+            "ip_address": "2604:A880:0800:0010:0000:0000:02DD:4001",
+            "netmask": 64,
+            "gateway": "2604:A880:0800:0010:0000:0000:0000:0001",
+            "type": "public"
+          }
+        ]
+      },
+      "region": {
+        "name": "New York 3",
+        "slug": "nyc3",
+        "sizes": [
+
+        ],
+        "features": [
+          "virtio",
+          "private_networking",
+          "backups",
+          "ipv6",
+          "metadata"
+        ],
+        "available": null
+      }
+    }
+  ],
+  "links": {
+    "pages":
+      {
+        "last":"https://api.digitalocean.com/v2/droplets?page=2",
+        "next":"https://api.digitalocean.com/v2/droplets?page=2"
+      }
+  },
+  "meta": {
+    "total":2
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/list_sizes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/list_sizes.json b/libcloud/test/compute/fixtures/digitalocean_v2/list_sizes.json
new file mode 100644
index 0000000..9e62c4d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/list_sizes.json
@@ -0,0 +1,35 @@
+{
+  "sizes": [
+    {
+      "slug": "512mb",
+      "memory": 512,
+      "vcpus": 1,
+      "disk": 20,
+      "transfer": 1,
+      "price_monthly": 5.0,
+      "price_hourly": 0.00744,
+      "regions": [
+        "nyc1",
+        "ams1",
+        "sfo1"
+      ]
+    },
+    {
+      "slug": "1gb",
+      "memory": 1024,
+      "vcpus": 2,
+      "disk": 30,
+      "transfer": 2,
+      "price_monthly": 10.0,
+      "price_hourly": 0.01488,
+      "regions": [
+        "nyc1",
+        "ams1",
+        "sfo1"
+      ]
+    }
+  ],
+  "meta": {
+    "total": 2
+  }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/fixtures/digitalocean_v2/reboot_node.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/digitalocean_v2/reboot_node.json b/libcloud/test/compute/fixtures/digitalocean_v2/reboot_node.json
new file mode 100644
index 0000000..7e1d067
--- /dev/null
+++ b/libcloud/test/compute/fixtures/digitalocean_v2/reboot_node.json
@@ -0,0 +1,13 @@
+{
+  "action":
+    {
+      "id":918910,
+      "status":"in-progress",
+      "type":"reboot",
+      "started_at":"2014-12-21T02:19:17Z",
+      "completed_at":null,
+      "resource_id":12345,
+      "resource_type":"droplet",
+      "region":"nyc3"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/libcloud/test/compute/test_digitalocean.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_digitalocean.py b/libcloud/test/compute/test_digitalocean.py
deleted file mode 100644
index ff4159b..0000000
--- a/libcloud/test/compute/test_digitalocean.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-import sys
-import unittest
-
-try:
-    import simplejson as json
-except ImportError:
-    import json  # NOQA
-
-from libcloud.utils.py3 import httplib
-
-from libcloud.common.types import InvalidCredsError
-from libcloud.compute.base import NodeImage
-from libcloud.compute.drivers.digitalocean import DigitalOceanNodeDriver
-
-from libcloud.test import LibcloudTestCase, MockHttpTestCase
-from libcloud.test.file_fixtures import ComputeFileFixtures
-from libcloud.test.secrets import DIGITAL_OCEAN_PARAMS
-
-
-# class DigitalOceanTests(unittest.TestCase, TestCaseMixin):
-class DigitalOceanTests(LibcloudTestCase):
-
-    def setUp(self):
-        DigitalOceanNodeDriver.connectionCls.conn_classes = \
-            (None, DigitalOceanMockHttp)
-        DigitalOceanMockHttp.type = None
-        self.driver = DigitalOceanNodeDriver(*DIGITAL_OCEAN_PARAMS)
-
-    def test_authentication(self):
-        DigitalOceanMockHttp.type = 'UNAUTHORIZED_CLIENT'
-        self.assertRaises(InvalidCredsError, self.driver.list_nodes)
-
-    def test_list_images_success(self):
-        images = self.driver.list_images()
-        self.assertTrue(len(images) >= 1)
-
-        image = images[0]
-        self.assertTrue(image.id is not None)
-        self.assertTrue(image.name is not None)
-
-    def test_list_sizes_success(self):
-        sizes = self.driver.list_sizes()
-        self.assertTrue(len(sizes) >= 1)
-
-        size = sizes[0]
-        self.assertTrue(size.id is not None)
-        self.assertEqual(size.name, '512MB')
-        self.assertEqual(size.ram, 512)
-
-        size = sizes[4]
-        self.assertTrue(size.id is not None)
-        self.assertEqual(size.name, '8GB')
-        self.assertEqual(size.ram, 8 * 1024)
-
-    def test_list_locations_success(self):
-        locations = self.driver.list_locations()
-        self.assertTrue(len(locations) >= 1)
-
-        location = locations[0]
-        self.assertEqual(location.id, '1')
-        self.assertEqual(location.name, 'New York 1')
-
-    def test_list_nodes_success(self):
-        nodes = self.driver.list_nodes()
-        self.assertEqual(len(nodes), 1)
-        self.assertEqual(nodes[0].name, 'test-2')
-        self.assertEqual(nodes[0].public_ips, [])
-        self.assertEqual(nodes[0].extra['image_id'], 1601)
-        self.assertEqual(nodes[0].extra['size_id'], 66)
-
-    def test_create_node_invalid_size(self):
-        image = NodeImage(id='invalid', name=None, driver=self.driver)
-        size = self.driver.list_sizes()[0]
-        location = self.driver.list_locations()[0]
-
-        DigitalOceanMockHttp.type = 'INVALID_IMAGE'
-        expected_msg = r'You specified an invalid image for Droplet creation. \(code: 404\)'
-        self.assertRaisesRegexp(Exception, expected_msg,
-                                self.driver.create_node,
-                                name='test', size=size, image=image,
-                                location=location)
-
-    def test_reboot_node_success(self):
-        node = self.driver.list_nodes()[0]
-        result = self.driver.reboot_node(node)
-        self.assertTrue(result)
-
-    def test_destroy_node_success(self):
-        node = self.driver.list_nodes()[0]
-        result = self.driver.destroy_node(node)
-        self.assertTrue(result)
-
-    def test_ex_rename_node_success(self):
-        node = self.driver.list_nodes()[0]
-        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()
-        self.assertEqual(len(keys), 1)
-
-        self.assertEqual(keys[0].id, 7717)
-        self.assertEqual(keys[0].name, 'test1')
-        self.assertEqual(keys[0].pub_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)
-        self.assertTrue(result)
-
-
-class DigitalOceanMockHttp(MockHttpTestCase):
-    fixtures = ComputeFileFixtures('digitalocean')
-
-    def _regions(self, method, url, body, headers):
-        body = self.fixtures.load('list_locations.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _images(self, method, url, body, headers):
-        body = self.fixtures.load('list_images.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _sizes(self, method, url, body, headers):
-        body = self.fixtures.load('list_sizes.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _droplets(self, method, url, body, headers):
-        body = self.fixtures.load('list_nodes.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _droplets_new_INVALID_IMAGE(self, method, url, body, headers):
-        # reboot_node
-        body = self.fixtures.load('error_invalid_image.json')
-        return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.NOT_FOUND])
-
-    def _droplets_119461_reboot(self, method, url, body, headers):
-        # reboot_node
-        body = self.fixtures.load('reboot_node.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _droplets_119461_destroy(self, method, url, body, headers):
-        # destroy_node
-        self.assertUrlContainsQueryParams(url, {'scrub_data': '1'})
-        body = self.fixtures.load('destroy_node.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _droplets_119461_rename(self, method, url, body, headers):
-        # reboot_node
-        self.assertUrlContainsQueryParams(url, {'name': 'fedora helios'})
-        body = self.fixtures.load('ex_rename_node.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _ssh_keys(self, method, url, body, headers):
-        body = self.fixtures.load('ex_list_ssh_keys.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _ssh_keys_7717_destroy(self, method, url, body, headers):
-        # destroy_ssh_key
-        body = self.fixtures.load('ex_destroy_ssh_key.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _droplets_UNAUTHORIZED_CLIENT(self, method, url, body, headers):
-        body = self.fixtures.load('error.txt')
-        return (httplib.FOUND, body, {}, httplib.responses[httplib.FOUND])
-
-if __name__ == '__main__':
-    sys.exit(unittest.main())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/92fa2860/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
new file mode 100644
index 0000000..030e2ee
--- /dev/null
+++ b/libcloud/test/compute/test_digitalocean_v1.py
@@ -0,0 +1,182 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import sys
+import unittest
+
+try:
+    import simplejson as json
+except ImportError:
+    import json  # NOQA
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.types import InvalidCredsError
+from libcloud.compute.base import NodeImage
+from libcloud.compute.drivers.digitalocean import DigitalOceanNodeDriver
+
+from libcloud.test import LibcloudTestCase, MockHttpTestCase
+from libcloud.test.file_fixtures import ComputeFileFixtures
+from libcloud.test.secrets import DIGITALOCEAN_v1_PARAMS
+
+
+# class DigitalOceanTests(unittest.TestCase, TestCaseMixin):
+class DigitalOcean_v1_Tests(LibcloudTestCase):
+
+    def setUp(self):
+        DigitalOceanNodeDriver.connectionCls.conn_classes = \
+            (None, DigitalOceanMockHttp)
+        DigitalOceanMockHttp.type = None
+        self.driver = DigitalOceanNodeDriver(*DIGITALOCEAN_v1_PARAMS,
+                                             api_version='v1')
+
+    def test_authentication(self):
+        DigitalOceanMockHttp.type = 'UNAUTHORIZED_CLIENT'
+        self.assertRaises(InvalidCredsError, self.driver.list_nodes)
+
+    def test_list_images_success(self):
+        images = self.driver.list_images()
+        self.assertTrue(len(images) >= 1)
+
+        image = images[0]
+        self.assertTrue(image.id is not None)
+        self.assertTrue(image.name is not None)
+
+    def test_list_sizes_success(self):
+        sizes = self.driver.list_sizes()
+        self.assertTrue(len(sizes) >= 1)
+
+        size = sizes[0]
+        self.assertTrue(size.id is not None)
+        self.assertEqual(size.name, '512MB')
+        self.assertEqual(size.ram, 512)
+
+        size = sizes[4]
+        self.assertTrue(size.id is not None)
+        self.assertEqual(size.name, '8GB')
+        self.assertEqual(size.ram, 8 * 1024)
+
+    def test_list_locations_success(self):
+        locations = self.driver.list_locations()
+        self.assertTrue(len(locations) >= 1)
+
+        location = locations[0]
+        self.assertEqual(location.id, '1')
+        self.assertEqual(location.name, 'New York 1')
+
+    def test_list_nodes_success(self):
+        nodes = self.driver.list_nodes()
+        self.assertEqual(len(nodes), 1)
+        self.assertEqual(nodes[0].name, 'test-2')
+        self.assertEqual(nodes[0].public_ips, [])
+        self.assertEqual(nodes[0].extra['image_id'], 1601)
+        self.assertEqual(nodes[0].extra['size_id'], 66)
+
+    def test_create_node_invalid_size(self):
+        image = NodeImage(id='invalid', name=None, driver=self.driver)
+        size = self.driver.list_sizes()[0]
+        location = self.driver.list_locations()[0]
+
+        DigitalOceanMockHttp.type = 'INVALID_IMAGE'
+        expected_msg = r'You specified an invalid image for Droplet creation. \(code: 404\)'
+        self.assertRaisesRegexp(Exception, expected_msg,
+                                self.driver.create_node,
+                                name='test', size=size, image=image,
+                                location=location)
+
+    def test_reboot_node_success(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.reboot_node(node)
+        self.assertTrue(result)
+
+    def test_destroy_node_success(self):
+        node = self.driver.list_nodes()[0]
+        result = self.driver.destroy_node(node)
+        self.assertTrue(result)
+
+    def test_ex_rename_node_success(self):
+        node = self.driver.list_nodes()[0]
+        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()
+        self.assertEqual(len(keys), 1)
+
+        self.assertEqual(keys[0].id, 7717)
+        self.assertEqual(keys[0].name, 'test1')
+        self.assertEqual(keys[0].pub_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)
+        self.assertTrue(result)
+
+
+class DigitalOceanMockHttp(MockHttpTestCase):
+    fixtures = ComputeFileFixtures('digitalocean_v1')
+
+    def _v1_regions(self, method, url, body, headers):
+        body = self.fixtures.load('list_locations.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_images(self, method, url, body, headers):
+        body = self.fixtures.load('list_images.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_sizes(self, method, url, body, headers):
+        body = self.fixtures.load('list_sizes.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_droplets(self, method, url, body, headers):
+        body = self.fixtures.load('list_nodes.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_droplets_new_INVALID_IMAGE(self, method, url, body, headers):
+        # reboot_node
+        body = self.fixtures.load('error_invalid_image.json')
+        return (httplib.NOT_FOUND, body, {}, httplib.responses[httplib.NOT_FOUND])
+
+    def _v1_droplets_119461_reboot(self, method, url, body, headers):
+        # reboot_node
+        body = self.fixtures.load('reboot_node.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_droplets_119461_destroy(self, method, url, body, headers):
+        # destroy_node
+        self.assertUrlContainsQueryParams(url, {'scrub_data': '1'})
+        body = self.fixtures.load('destroy_node.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_droplets_119461_rename(self, method, url, body, headers):
+        # reboot_node
+        self.assertUrlContainsQueryParams(url, {'name': 'fedora helios'})
+        body = self.fixtures.load('ex_rename_node.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_ssh_keys(self, method, url, body, headers):
+        body = self.fixtures.load('ex_list_ssh_keys.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_ssh_keys_7717_destroy(self, method, url, body, headers):
+        # destroy_ssh_key
+        body = self.fixtures.load('ex_destroy_ssh_key.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _v1_droplets_UNAUTHORIZED_CLIENT(self, method, url, body, headers):
+        body = self.fixtures.load('error.txt')
+        return (httplib.FOUND, body, {}, httplib.responses[httplib.FOUND])
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())


Mime
View raw message