~ed.so/duplicity/manpage

« back to all changes in this revision

Viewing changes to duplicity/backends/u1backend.py

  • Committer: "Kenneth Loafman"
  • Date: 2012-10-27 12:16:19 UTC
  • Revision ID: kenneth@loafman.com-20121027121619-j9aesrw1nh2oxpe3
- Applied patch from az for bug #1066625 u1backend
  + add delay between retries

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
#
3
3
# Copyright 2011 Canonical Ltd
4
4
# Authors: Michael Terry <michael.terry@canonical.com>
5
 
#                  Alexander Zangerl <az@debian.org>
 
5
#          Alexander Zangerl <az@debian.org>
6
6
#
7
7
# This file is part of duplicity.
8
8
#
32
32
import getpass
33
33
import os
34
34
import sys
 
35
import time
35
36
 
36
37
class OAuthHttpClient(object):
37
38
    """a simple HTTP client with OAuth added on"""
46
47
                                            consumer_secret)
47
48
 
48
49
    def set_token(self, token, token_secret):
49
 
        self.token = oauth.OAuthToken(token, token_secret)
 
50
        self.token = oauth.OAuthToken( token, token_secret)
50
51
 
51
52
    def _get_oauth_request_header(self, url, method):
52
53
        """Get an oauth request header given the token and the url"""
53
54
        query = urlparse(url).query
54
55
 
55
56
        oauth_request = oauth.OAuthRequest.from_consumer_and_token(
56
 
            http_url = url,
57
 
            http_method = method,
58
 
            oauth_consumer = self.consumer,
59
 
            token = self.token,
60
 
            parameters = dict(parse_qsl(query))
 
57
            http_url=url,
 
58
            http_method=method,
 
59
            oauth_consumer=self.consumer,
 
60
            token=self.token,
 
61
            parameters=dict(parse_qsl(query))
61
62
        )
62
63
        oauth_request.sign_request(oauth.OAuthSignatureMethod_HMAC_SHA1(),
63
64
                                   self.consumer, self.token)
64
65
        return oauth_request.to_header()
65
66
 
66
 
    def request(self, url, method = "GET", body = None, headers = {}, ignore = None):
 
67
    def request(self, url, method="GET", body=None, headers={}, ignore=None):
67
68
        oauth_header = self._get_oauth_request_header(url, method)
68
69
        headers.update(oauth_header)
69
70
 
70
 
        for n in range(1, globals.num_retries + 1):
71
 
            log.Info("making %s request to %s (attempt %d)" % (method, url, n))
 
71
        for n in range(1, globals.num_retries+1):
 
72
            log.Info("making %s request to %s (attempt %d)" % (method,url,n))
72
73
            try:
73
 
                resp, content = self.client.request(url, method, headers = headers, body = body)
 
74
                resp, content = self.client.request(url, method, headers=headers, body=body)
74
75
            except Exception, e:
75
 
                log.Info("request failed, exception %s" % e)
76
 
                if n >= globals.num_retries + 1:
77
 
                    log.FatalError("Giving up on request after %d attempts, last exception %s" % (n, e))
 
76
                log.Info("request failed, exception %s" % e);
 
77
                if n == globals.num_retries:
 
78
                    log.FatalError("Giving up on request after %d attempts, last exception %s" % (n,e))
 
79
                time.sleep(30)
78
80
                continue
79
81
 
80
 
            log.Info("completed request with status %s %s" % (resp.status, resp.reason))
 
82
            log.Info("completed request with status %s %s" % (resp.status,resp.reason))
81
83
            oops_id = resp.get('x-oops-id', None)
82
84
            if oops_id:
83
85
                log.Debug("Server Error: method %s url %s Oops-ID %s" % (method, url, oops_id))
86
88
                content = loads(content)
87
89
 
88
90
            # were we successful? status either 2xx or code we're told to ignore
89
 
            numcode = int(resp.status)
90
 
            if (numcode >= 200 and numcode < 300) or (ignore and numcode in ignore):
 
91
            numcode=int(resp.status)
 
92
            if (numcode>=200 and numcode<300) or (ignore and numcode in ignore):
91
93
                return resp, content
92
94
 
93
95
            ecode = log.ErrorCode.backend_error
96
98
            elif numcode == 404:
97
99
                ecode = log.ErrorCode.backend_not_found
98
100
 
99
 
            if n >= globals.num_retries + 1:
100
 
                log.FatalError("Giving up on request after %d attempts, last status %d %s" % (n, numcode.resp.reason), ecode)
101
 
 
102
 
 
103
 
    def get_and_set_token(self, email, password, hostname):
 
101
            if n < globals.num_retries:
 
102
                time.sleep(30)
 
103
 
 
104
        log.FatalError("Giving up on request after %d attempts, last status %d %s" % (n,numcode,resp.reason),
 
105
                       ecode)
 
106
 
 
107
 
 
108
    def get_and_set_token(self,email, password, hostname):
104
109
        """Acquire an Ubuntu One access token via OAuth with the Ubuntu SSO service.
105
110
        See https://one.ubuntu.com/developer/account_admin/auth/otherplatforms for details.
106
111
        """
107
112
 
108
113
        # Request new access token from the Ubuntu SSO service
109
 
        self.client.add_credentials(email, password)
 
114
        self.client.add_credentials(email,password)
110
115
        resp, content = self.client.request('https://login.ubuntu.com/api/1.0/authentications?'
111
 
                                            + 'ws.op=authenticate&token_name=Ubuntu%%20One%%20@%%20%s' % hostname)
112
 
        if resp.status != 200:
113
 
            log.FatalError("Token request failed: Incorrect Ubuntu One credentials", log.ErrorCode.backend_permission_denied)
 
116
                                            +'ws.op=authenticate&token_name=Ubuntu%%20One%%20@%%20%s' % hostname)
 
117
        if resp.status!=200:
 
118
            log.FatalError("Token request failed: Incorrect Ubuntu One credentials",log.ErrorCode.backend_permission_denied)
114
119
            self.client.clear_credentials()
115
120
 
116
 
        tokendata = loads(content)
117
 
        self.set_consumer(tokendata['consumer_key'], tokendata['consumer_secret'])
118
 
        self.set_token(tokendata['token'], tokendata['token_secret'])
 
121
        tokendata=loads(content)
 
122
        self.set_consumer(tokendata['consumer_key'],tokendata['consumer_secret'])
 
123
        self.set_token(tokendata['token'],tokendata['token_secret'])
119
124
 
120
125
        # and finally tell Ubuntu One about the token
121
126
        resp, content = self.request('https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/')
122
 
        if resp.status != 200:
123
 
            log.FatalError("Ubuntu One token was not accepted: %s %s" % (resp.status, resp.reason))
 
127
        if resp.status!=200:
 
128
            log.FatalError("Ubuntu One token was not accepted: %s %s" % (resp.status,resp.reason))
124
129
 
125
130
        return tokendata
126
131
 
141
146
        self.volume_uri = "%s/volumes/~/%s" % (self.api_base, path)
142
147
        self.meta_base = "%s/~/%s/" % (self.api_base, path)
143
148
 
144
 
        self.client = OAuthHttpClient()
 
149
        self.client=OAuthHttpClient();
145
150
 
146
151
        if 'FTP_PASSWORD' not in os.environ:
147
152
            sys.stderr.write("No Ubuntu One token found in $FTP_PASSWORD, requesting a new one\n")
148
 
            email = raw_input('Enter Ubuntu One account email: ')
149
 
            password = getpass.getpass("Enter Ubuntu One password: ")
150
 
            hostname = os.uname()[1]
 
153
            email=raw_input('Enter Ubuntu One account email: ')
 
154
            password=getpass.getpass("Enter Ubuntu One password: ")
 
155
            hostname=os.uname()[1]
151
156
 
152
 
            tokendata = self.client.get_and_set_token(email, password, hostname)
 
157
            tokendata=self.client.get_and_set_token(email,password,hostname)
153
158
            sys.stderr.write("\nPlease record your new Ubuntu One access token for future use with duplicity:\n"
154
 
                             + "FTP_PASSWORD=%s:%s:%s:%s\n\n"
155
 
                             % (tokendata['consumer_key'], tokendata['consumer_secret'],
156
 
                                tokendata['token'], tokendata['token_secret']))
 
159
                             +"FTP_PASSWORD=%s:%s:%s:%s\n\n"
 
160
                             % (tokendata['consumer_key'],tokendata['consumer_secret'],
 
161
                                tokendata['token'],tokendata['token_secret']))
157
162
        else:
158
 
            (consumer, consumer_secret, token, token_secret) = os.environ['FTP_PASSWORD'].split(':')
 
163
            (consumer,consumer_secret,token,token_secret) = os.environ['FTP_PASSWORD'].split(':')
159
164
            self.client.set_consumer(consumer, consumer_secret)
160
165
            self.client.set_token(token, token_secret)
161
166
 
162
 
        resp, content = self.client.request(self.api_base, ignore = [400, 401, 403])
163
 
        if resp['status'] != '200':
164
 
            log.FatalError("Access failed: Ubuntu One credentials incorrect",
 
167
        resp, content = self.client.request(self.api_base,ignore=[400,401,403])
 
168
        if resp['status']!='200':
 
169
           log.FatalError("Access failed: Ubuntu One credentials incorrect",
165
170
                           log.ErrorCode.user_error)
166
171
 
167
172
        # Create volume, but check existence first
168
 
        resp, content = self.client.request(self.volume_uri, ignore = [404])
169
 
        if resp['status'] == '404':
170
 
            resp, content = self.client.request(self.volume_uri, "PUT")
 
173
        resp, content = self.client.request(self.volume_uri,ignore=[404])
 
174
        if resp['status']=='404':
 
175
            resp, content = self.client.request(self.volume_uri,"PUT")
171
176
 
172
177
    def quote(self, url):
173
 
        return urllib.quote(url, safe = "/~").replace(" ", "%20")
 
178
        return urllib.quote(url, safe="/~").replace(" ","%20")
174
179
 
175
180
    def put(self, source_path, remote_filename = None):
176
181
        """Copy file to remote"""
178
183
            remote_filename = source_path.get_filename()
179
184
        remote_full = self.meta_base + self.quote(remote_filename)
180
185
        # check if it exists already, returns existing content_path
181
 
        resp, content = self.client.request(remote_full, ignore = [404])
182
 
        if resp['status'] == '404':
 
186
        resp, content = self.client.request(remote_full,ignore=[404])
 
187
        if resp['status']=='404':
183
188
            # put with path returns new content_path
184
189
            resp, content = self.client.request(remote_full,
185
 
                                                method = "PUT",
 
190
                                                method="PUT",
186
191
                                                headers = { 'content-type': 'application/json' },
187
 
                                                body = dumps({"kind":"file"}))
188
 
        elif resp['status'] != '200':
 
192
                                                body=dumps({"kind":"file"}))
 
193
        elif resp['status']!='200':
189
194
            raise BackendException("access to %s failed, code %s" % (remote_filename, resp['status']))
190
195
 
191
196
        assert(content['content_path'] is not None)
193
198
        remote_full = self.content_base + self.quote(content['content_path'])
194
199
        log.Info("uploading file %s to location %s" % (remote_filename, remote_full))
195
200
 
196
 
        fh = open(source_path.name, 'rb')
 
201
        fh=open(source_path.name,'rb')
197
202
        data = bytearray(fh.read())
198
203
        fh.close()
199
204
 
201
206
        headers = {"Content-Length": str(len(data)),
202
207
                   "Content-Type": content_type}
203
208
        resp, content = self.client.request(remote_full,
204
 
                                            method = "PUT",
205
 
                                            body = str(data),
206
 
                                            headers = headers)
 
209
                                            method="PUT",
 
210
                                            body=str(data),
 
211
                                            headers=headers)
207
212
 
208
213
    def get(self, filename, local_path):
209
214
        """Get file and put in local_path (Path object)"""
242
247
 
243
248
        for filename in filename_list:
244
249
            remote_full = self.meta_base + self.quote(filename)
245
 
            resp, content = self.client.request(remote_full, method = "DELETE")
 
250
            resp, content = self.client.request(remote_full,method="DELETE")
246
251
 
247
252
    def _query_file_info(self, filename):
248
253
        """Query attributes on filename"""