Merge lp:~verb/duplicity/boto-gcs into lp:duplicity/0.6

Proposed by Lee Verberne
Status: Merged
Merged at revision: 922
Proposed branch: lp:~verb/duplicity/boto-gcs
Merge into: lp:duplicity/0.6
Diff against target: 208 lines (+64/-12)
5 files modified
bin/duplicity.1 (+25/-2)
duplicity/backends/_boto_single.py (+14/-10)
testing/manual/backendtest.py (+13/-0)
testing/manual/config.py.tmpl (+10/-0)
testing/tests/test_tarfile.py (+2/-0)
To merge this branch: bzr merge lp:~verb/duplicity/boto-gcs
Reviewer Review Type Date Requested Status
duplicity-team Pending
Review via email: mp+178399@code.launchpad.net

Description of the change

These patches add support for Google Cloud Storage via the boto backend.

boto has supported GCS in interoperability mode for a few years now. This change adds support by taking advantage of boto's storage_uri abstraction layer.

To post a comment you must log in.
Revision history for this message
edso (ed.so) wrote :

nice that you documented the new feature properly!

quick question:

you changed quite a bit in the boto backend. did you verify it still work flawlessly in both modes with s3?

..ede/duply.net

Revision history for this message
Lee Verberne (verb) wrote :

That's a good question. I wrote the patches working with S3 first, and then verifying functionality on GCS afterwards.

That being said, I'd appreciate your advice on what else I should test.

Here's what I've verified so far:

 * testing/run-tests unit tests
 * testing/manual/backendtest.py unit tests
 * manual backup/restore of a large file to S3 using the default flags and comparing checksums after completion
 * manual backup/restore of a large file to S3 using --s3-use-new-style and comparing checksums after completion
 * manual backup/restore of a large file to GCS using the default flags and comparing checksums after completion
 * manual backup/restore of a large file to GCS using --s3-use-new-style and comparing checksums after completion
 * Interchanging the s3:// and s3+http:// duplicity address syntax

I haven't tested --s3-unencrypted-connections or --s3-european-buckets. I also haven't tested older versions of boto, but that's probably something I should do. It looks like 1.6a is the current minimum version, so I'll test that and get back to you.

Revision history for this message
Lee Verberne (verb) wrote :

Ok, so boto didn't add support for GCS until 2.0, which was released on 13-Jul-2011. My changes work with boto 2.0.

The docs list boto 2.1.1 as a requirement for S3 multiprocessing and boto 1.6a for S3 single processing. Merging this would require bumping up the minimum version of boto for single processing to 2.0.

Otherwise I would either have to separate out parts of the GCS & S3 code paths in the boto backend, which would muddy up the code a bit and defeat the point of storage_uri, or remove GCS to a separate backend.

Does anyone have an opinion on minimum version requirements?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/duplicity.1'
2--- bin/duplicity.1 2013-04-27 14:10:11 +0000
3+++ bin/duplicity.1 2013-08-02 23:03:25 +0000
4@@ -16,7 +16,7 @@
5
6 Some backends also require additional components (probably available as packages for your specific platform):
7 .TP
8-.BR "boto backend" " (S3 Amazon Web Services)"
9+.BR "boto backend" " (S3 Amazon Web Services, Google Cloud Storage)"
10 .B boto
11 - http://github.com/boto/boto
12 .TP
13@@ -936,7 +936,7 @@
14 it is permitted however.
15 Consider setting the environment variable
16 .B FTP_PASSWORD
17-instead, which is used by most, if not all backends, regardless of it's name.
18+instead, which is used by most, if not all backends, regardless of its name.
19 .PP
20 In protocols that support it, the path may be preceded by a single
21 slash, '/path', to represent a relative path to the target home directory,
22@@ -957,6 +957,10 @@
23 .PP
24 gdocs://user[:password]@other.host/some_dir
25 .PP
26+.BI "Google Cloud Storage"
27+.br
28+gs://bucket[/prefix]
29+.PP
30 hsi://user[:password]@other.host/some_dir
31 .PP
32 imap[s]://user[:password]@host.com[/from_address_prefix]
33@@ -1363,6 +1367,25 @@
34 or HTTP errors when trying to upload files to your newly created
35 bucket. Give it a few minutes and the bucket should function normally.
36
37+.SH A NOTE ON GOOGLE CLOUD STORAGE
38+Support for Google Cloud Storage relies on its Interoperable Access,
39+which must be enabled for your account. Once enabled, you can generate
40+Interoperable Storage Access Keys and pass them to duplicity via the
41+.B GS_ACCESS_KEY_ID
42+and
43+.B GS_SECRET_ACCESS_KEY
44+environment variables. Alternatively, you can run
45+.B "gsutil config -a"
46+to have the Google Cloud Storage utility populate the
47+.B ~/.boto
48+configuration file.
49+.PP
50+Enable Interoperable Access:
51+https://code.google.com/apis/console#:storage
52+.br
53+Create Access Keys:
54+https://code.google.com/apis/console#:storage:legacy
55+
56 .SH A NOTE ON IMAP
57 An IMAP account can be used as a target for the upload. The userid may
58 be specified and the password will be requested.
59
60=== modified file 'duplicity/backends/_boto_single.py'
61--- duplicity/backends/_boto_single.py 2013-01-25 13:35:33 +0000
62+++ duplicity/backends/_boto_single.py 2013-08-02 23:03:25 +0000
63@@ -50,8 +50,6 @@
64 import boto
65 assert boto.Version >= BOTO_MIN_VERSION
66
67- from boto.s3.key import Key
68-
69 # This folds the null prefix and all null parts, which means that:
70 # //MyBucket/ and //MyBucket are equivalent.
71 # //MyBucket//My///My/Prefix/ and //MyBucket/My/Prefix are equivalent.
72@@ -66,8 +64,6 @@
73
74 self.scheme = parsed_url.scheme
75
76- self.key_class = Key
77-
78 if self.url_parts:
79 self.key_prefix = '%s/' % '/'.join(self.url_parts)
80 else:
81@@ -75,6 +71,12 @@
82
83 self.straight_url = duplicity.backend.strip_auth_from_url(parsed_url)
84 self.parsed_url = parsed_url
85+
86+ # duplicity and boto.storage_uri() have different URI formats.
87+ # boto uses scheme://bucket[/name] and specifies hostname on connect()
88+ self.boto_uri_str = '://'.join((parsed_url.scheme[:2],
89+ parsed_url.path.lstrip('/')))
90+ self.storage_uri = boto.storage_uri(self.boto_uri_str)
91 self.resetConnection()
92
93 def resetConnection(self):
94@@ -140,12 +142,13 @@
95 "(http://code.google.com/p/boto/)." % BOTO_MIN_VERSION,
96 log.ErrorCode.boto_lib_too_old)
97
98- if self.scheme == 's3+http':
99- # Use the default Amazon S3 host.
100- self.conn = S3Connection(is_secure=(not globals.s3_unencrypted_connection))
101+ if not self.parsed_url.hostname:
102+ # Use the default host.
103+ self.conn = self.storage_uri.connect(
104+ is_secure=(not globals.s3_unencrypted_connection))
105 else:
106 assert self.scheme == 's3'
107- self.conn = S3Connection(
108+ self.conn = self.storage_uri.connect(
109 host=self.parsed_url.hostname,
110 is_secure=(not globals.s3_unencrypted_connection))
111
112@@ -199,7 +202,7 @@
113
114 if not remote_filename:
115 remote_filename = source_path.get_filename()
116- key = self.key_class(self.bucket)
117+ key = self.storage_uri.new_key()
118 key.key = self.key_prefix + remote_filename
119
120 for n in range(1, globals.num_retries+1):
121@@ -236,7 +239,7 @@
122 raise BackendException("Error uploading %s/%s" % (self.straight_url, remote_filename))
123
124 def get(self, remote_filename, local_path):
125- key = self.key_class(self.bucket)
126+ key = self.storage_uri.new_key()
127 key.key = self.key_prefix + remote_filename
128 for n in range(1, globals.num_retries+1):
129 if n > 1:
130@@ -326,5 +329,6 @@
131 else:
132 return {'size': None}
133
134+duplicity.backend.register_backend("gs", BotoBackend)
135 duplicity.backend.register_backend("s3", BotoBackend)
136 duplicity.backend.register_backend("s3+http", BotoBackend)
137
138=== modified file 'testing/manual/backendtest.py'
139--- testing/manual/backendtest.py 2011-11-17 15:59:54 +0000
140+++ testing/manual/backendtest.py 2013-08-02 23:03:25 +0000
141@@ -188,6 +188,19 @@
142 password = config.ftp_password
143
144
145+class gsModuleTest(unittest.TestCase, UnivTest):
146+ """ Test the gs module backend """
147+ def setUp(self):
148+ assert not os.system("tar xzf testfiles.tar.gz > /dev/null 2>&1")
149+
150+ def tearDown(self):
151+ assert not os.system("rm -rf testfiles tempdir temp2.tar")
152+
153+ my_test_id = "gs/boto"
154+ url_string = config.gs_url
155+ password = None
156+
157+
158 class rsyncAbsPathTest(unittest.TestCase, UnivTest):
159 """ Test the rsync abs path backend """
160 def setUp(self):
161
162=== modified file 'testing/manual/config.py.tmpl'
163--- testing/manual/config.py.tmpl 2012-09-13 14:08:52 +0000
164+++ testing/manual/config.py.tmpl 2013-08-02 23:03:25 +0000
165@@ -65,6 +65,10 @@
166 ftp_url = None
167 ftp_password = None
168
169+gs_url = None
170+gs_access_key = None
171+gs_secret_key = None
172+
173 rsync_abspath_url = None
174 rsync_relpath_url = None
175 rsync_module_url = None
176@@ -108,6 +112,12 @@
177
178 set_environ("FTP_PASSWORD", None)
179 set_environ("PASSPHRASE", None)
180+ if gs_access_key:
181+ set_environ("GS_ACCESS_KEY_ID", gs_access_key)
182+ set_environ("GS_SECRET_ACCESS_KEY", gs_secret_key)
183+ else:
184+ set_environ("GS_ACCESS_KEY_ID", None)
185+ set_environ("GS_SECRET_ACCESS_KEY", None)
186 if s3_access_key:
187 set_environ("AWS_ACCESS_KEY_ID", s3_access_key)
188 set_environ("AWS_SECRET_ACCESS_KEY", s3_secret_key)
189
190=== modified file 'testing/tests/test_tarfile.py'
191--- testing/tests/test_tarfile.py 2011-11-04 04:27:29 +0000
192+++ testing/tests/test_tarfile.py 2013-08-02 23:03:25 +0000
193@@ -193,6 +193,7 @@
194 self.extract_and_compare_tarfile()
195
196 def extract_and_compare_tarfile(self):
197+ old_umask = os.umask(022)
198 os.system("rm -r tempdir")
199 assert not os.system("tar -xf temp2.tar")
200
201@@ -224,6 +225,7 @@
202 s = os.lstat("tempdir/symlink")
203 assert stat.S_ISLNK(s.st_mode)
204
205+ os.umask(old_umask)
206
207 class Test_FObj(BaseTest):
208 """Test for read operations via file-object.

Subscribers

People subscribed via source and target branches

to all changes: