You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by qu...@apache.org on 2017/10/16 06:28:32 UTC

[3/5] libcloud git commit: [LIBCLOUD-955] Add DigitalOcean Spaces support.

[LIBCLOUD-955] Add DigitalOcean Spaces support.

Signed-off-by: Quentin Pradet <qu...@apache.org>


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

Branch: refs/heads/trunk
Commit: 79052a080f87a436f27701db7429fa7dcb11d433
Parents: 820ff6f
Author: Andrew Starr-Bochicchio <a....@gmail.com>
Authored: Fri Oct 6 15:59:48 2017 -0400
Committer: Quentin Pradet <qu...@apache.org>
Committed: Mon Oct 16 10:24:58 2017 +0400

----------------------------------------------------------------------
 libcloud/storage/drivers/digitalocean_spaces.py | 101 +++++++++++++++
 libcloud/storage/providers.py                   |   3 +
 libcloud/storage/types.py                       |   1 +
 .../test/storage/test_digitalocean_spaces.py    | 122 +++++++++++++++++++
 4 files changed, 227 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/79052a08/libcloud/storage/drivers/digitalocean_spaces.py
----------------------------------------------------------------------
diff --git a/libcloud/storage/drivers/digitalocean_spaces.py b/libcloud/storage/drivers/digitalocean_spaces.py
new file mode 100644
index 0000000..1808b4d
--- /dev/null
+++ b/libcloud/storage/drivers/digitalocean_spaces.py
@@ -0,0 +1,101 @@
+# 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.
+
+from libcloud.common.types import LibcloudError
+from libcloud.common.aws import SignedAWSConnection, DEFAULT_SIGNATURE_VERSION
+from libcloud.storage.drivers.s3 import BaseS3Connection, S3Connection
+from libcloud.storage.drivers.s3 import S3StorageDriver, API_VERSION
+
+__all__ = [
+    'DigitalOceanSpacesStorageDriver'
+]
+
+DO_SPACES_HOSTS_BY_REGION = {'nyc3': 'nyc3.digitaloceanspaces.com'}
+
+DO_SPACES_DEFAULT_REGION = 'nyc3'
+
+
+class DOSpacesConnectionAWS4(SignedAWSConnection, BaseS3Connection):
+    service_name = 's3'
+    version = API_VERSION
+
+    def __init__(self, user_id, key, secure=True, host=None, port=None,
+                 url=None, timeout=None, proxy_url=None, token=None,
+                 retry_delay=None, backoff=None, **kwargs):
+
+        super(DOSpacesConnectionAWS4, self).__init__(user_id, key,
+                                                     secure, host,
+                                                     port, url,
+                                                     timeout,
+                                                     proxy_url, token,
+                                                     retry_delay,
+                                                     backoff,
+                                                     4)  # force aws4
+
+
+class DOSpacesConnectionAWS2(S3Connection):
+
+    def __init__(self, user_id, key, secure=True, host=None, port=None,
+                 url=None, timeout=None, proxy_url=None, token=None,
+                 retry_delay=None, backoff=None, **kwargs):
+
+        super(DOSpacesConnectionAWS2, self).__init__(user_id, key,
+                                                     secure, host,
+                                                     port, url,
+                                                     timeout,
+                                                     proxy_url, token,
+                                                     retry_delay,
+                                                     backoff)
+
+
+class DigitalOceanSpacesStorageDriver(S3StorageDriver):
+    name = 'DigitalOcean Spaces'
+    website = 'https://www.digitalocean.com/products/object-storage/'
+    supports_chunked_encoding = False
+    supports_s3_multipart_upload = True
+
+    def __init__(self, key, secret=None, secure=True, host=None, port=None,
+                 api_version=None, region=DO_SPACES_DEFAULT_REGION, **kwargs):
+
+        if region not in DO_SPACES_HOSTS_BY_REGION:
+            raise LibcloudError('Unknown region (%s)' % (region), driver=self)
+
+        host = DO_SPACES_HOSTS_BY_REGION[region]
+        self.name = 'DigitalOcean Spaces (%s)' % (region)
+
+        self.region_name = region
+        self.signature_version = str(kwargs.pop('signature_version',
+                                                DEFAULT_SIGNATURE_VERSION))
+
+        if self.signature_version not in ['2', '4']:
+            raise ValueError('Invalid signature_version: %s' %
+                             (self.signature_version))
+
+        if self.signature_version == '2':
+            self.connectionCls = DOSpacesConnectionAWS2
+        elif self.signature_version == '4':
+            self.connectionCls = DOSpacesConnectionAWS4
+        self.connectionCls.host = host
+
+        super(DigitalOceanSpacesStorageDriver,
+              self).__init__(key, secret,
+                             secure, host, port,
+                             api_version, region,
+                             **kwargs)
+
+    def _ex_connection_class_kwargs(self):
+        kwargs = {}
+        kwargs['signature_version'] = self.signature_version
+        return kwargs

http://git-wip-us.apache.org/repos/asf/libcloud/blob/79052a08/libcloud/storage/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/storage/providers.py b/libcloud/storage/providers.py
index 56b049e..183189e 100644
--- a/libcloud/storage/providers.py
+++ b/libcloud/storage/providers.py
@@ -81,6 +81,9 @@ DRIVERS = {
     ('libcloud.storage.drivers.backblaze_b2', 'BackblazeB2StorageDriver'),
     Provider.ALIYUN_OSS:
     ('libcloud.storage.drivers.oss', 'OSSStorageDriver'),
+    Provider.DIGITALOCEAN_SPACES:
+    ('libcloud.storage.drivers.digitalocean_spaces',
+     'DigitalOceanSpacesStorageDriver'),
 }
 
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/79052a08/libcloud/storage/types.py
----------------------------------------------------------------------
diff --git a/libcloud/storage/types.py b/libcloud/storage/types.py
index c093a0b..c9e7451 100644
--- a/libcloud/storage/types.py
+++ b/libcloud/storage/types.py
@@ -62,6 +62,7 @@ class Provider(object):
     AZURE_BLOBS = 'azure_blobs'
     BACKBLAZE_B2 = 'backblaze_b2'
     CLOUDFILES = 'cloudfiles'
+    DIGITALOCEAN_SPACES = 'digitalocean_spaces'
     GOOGLE_STORAGE = 'google_storage'
     KTUCLOUD = 'ktucloud'
     LOCAL = 'local'

http://git-wip-us.apache.org/repos/asf/libcloud/blob/79052a08/libcloud/test/storage/test_digitalocean_spaces.py
----------------------------------------------------------------------
diff --git a/libcloud/test/storage/test_digitalocean_spaces.py b/libcloud/test/storage/test_digitalocean_spaces.py
new file mode 100644
index 0000000..47f4779
--- /dev/null
+++ b/libcloud/test/storage/test_digitalocean_spaces.py
@@ -0,0 +1,122 @@
+# 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
+
+from libcloud.storage.base import Container, Object
+from libcloud.storage.drivers.digitalocean_spaces import (
+    DigitalOceanSpacesStorageDriver,
+    DOSpacesConnectionAWS4,
+    DOSpacesConnectionAWS2)
+
+from libcloud.test import LibcloudTestCase
+from libcloud.test.secrets import STORAGE_S3_PARAMS
+
+
+class DigitalOceanSpacesTests(LibcloudTestCase):
+    driver_type = DigitalOceanSpacesStorageDriver
+    driver_args = STORAGE_S3_PARAMS
+    default_host = 'nyc3.digitaloceanspaces.com'
+
+    @classmethod
+    def create_driver(self):
+        return self.driver_type(*self.driver_args,
+                                signature_version='2',
+                                host=self.default_host)
+
+    def setUp(self):
+        self.driver = self.create_driver()
+        self.container = Container('test-container', {}, self.driver)
+        self.object = Object('test-object', 1, 'hash', {},
+                             'meta_data', self.container, self.driver)
+
+    def test_connection_class_type(self):
+        res = self.driver.connectionCls is DOSpacesConnectionAWS2
+        self.assertTrue(res, 'driver.connectionCls does not match!')
+
+    def test_connection_class_host(self):
+        host = self.driver.connectionCls.host
+        self.assertEqual(host, self.default_host)
+
+    def test_container_enable_cdn_not_implemented(self):
+        with self.assertRaises(NotImplementedError):
+            self.container.enable_cdn()
+
+    def test_container_get_cdn_url_not_implemented(self):
+        with self.assertRaises(NotImplementedError):
+            self.container.get_cdn_url()
+
+    def test_object_enable_cdn_not_implemented(self):
+        with self.assertRaises(NotImplementedError):
+            self.object.enable_cdn()
+
+    def test_object_get_cdn_url_not_implemented(self):
+        with self.assertRaises(NotImplementedError):
+            self.object.get_cdn_url()
+
+
+class DigitalOceanSpacesTests_v4(DigitalOceanSpacesTests):
+    driver_type = DigitalOceanSpacesStorageDriver
+    driver_args = STORAGE_S3_PARAMS
+    default_host = 'nyc3.digitaloceanspaces.com'
+
+    @classmethod
+    def create_driver(self):
+        return self.driver_type(*self.driver_args,
+                                signature_version='4')
+
+    def test_connection_class_type(self):
+        res = self.driver.connectionCls is DOSpacesConnectionAWS4
+        self.assertTrue(res, 'driver.connectionCls does not match!')
+
+    def test_connection_class_host(self):
+        host = self.driver.connectionCls.host
+        self.assertEqual(host, self.default_host)
+
+
+class DigitalOceanSpacesDoubleInstanceTests(LibcloudTestCase):
+    driver_type = DigitalOceanSpacesStorageDriver
+    driver_args = STORAGE_S3_PARAMS
+    default_host = 'nyc3.digitaloceanspaces.com'
+
+    def setUp(self):
+        self.driver_v2 = self.driver_type(*self.driver_args,
+                                          signature_version='2')
+        self.driver_v4 = self.driver_type(*self.driver_args,
+                                          signature_version='4')
+
+    def test_connection_class_type(self):
+        res = self.driver_v2.connectionCls is DOSpacesConnectionAWS2
+        self.assertTrue(res, 'driver.connectionCls does not match!')
+
+        res = self.driver_v4.connectionCls is DOSpacesConnectionAWS4
+        self.assertTrue(res, 'driver.connectionCls does not match!')
+
+        # Verify again that connection class hasn't been overriden when
+        # instantiating a second driver class
+        res = self.driver_v2.connectionCls is DOSpacesConnectionAWS2
+        self.assertTrue(res, 'driver.connectionCls does not match!')
+
+    def test_connection_class_host(self):
+        host = self.driver_v2.connectionCls.host
+        self.assertEqual(host, self.default_host)
+
+        host = self.driver_v4.connectionCls.host
+        self.assertEqual(host, self.default_host)
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())