Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ b41a95a0

History | View | Annotate | Download (8.9 kB)

1
from httplib import HTTPConnection, HTTP
2
from sys import stdin
3

    
4
import json
5
import types
6
import socket
7

    
8
class Fault(Exception):
9
    def __init__(self, data=''):
10
        Exception.__init__(self, data)
11
        self.data = data
12

    
13

    
14
class Client(object):
15
    def __init__(self, host, account, api='v1', verbose=False, debug=False):
16
        """`host` can also include a port, e.g '127.0.0.1:8000'."""
17
        
18
        self.host = host
19
        self.account = account
20
        self.api = api
21
        self.verbose = verbose or debug
22
        self.debug = debug
23
    
24
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
25
                          blocksize=1024):
26
        http = HTTPConnection(self.host)
27
        
28
        # write header
29
        path = '/%s/%s%s' % (self.api, self.account, path)
30
        http.putrequest(method, path)
31
        http.putheader('Content-Type', 'application/octet-stream')
32
        http.putheader('Transfer-Encoding', 'chunked')
33
        if headers:
34
            for header,value in headers.items():
35
                http.putheader(header, value)
36
        http.endheaders()
37
        
38
        # write body
39
        data = ''
40
        while True:
41
            if f.closed:
42
                break
43
            block = f.read(blocksize)
44
            if block == '':
45
                break
46
            data = '%s\r\n%s\r\n' % (hex(len(block)), block)
47
            try:
48
                http.send(data)
49
            except:
50
                #retry
51
                http.send(data)
52
        data = '0x0\r\n'
53
        try:
54
            http.send(data)
55
        except:
56
            #retry
57
            http.send(data)
58
        
59
        # get response
60
        resp = http.getresponse()
61
        headers = dict(resp.getheaders())
62
        
63
        if self.verbose:
64
            print '%d %s' % (resp.status, resp.reason)
65
            for key, val in headers.items():
66
                print '%s: %s' % (key.capitalize(), val)
67
            print
68
        
69
        data = resp.read()
70
        if self.debug:
71
            print data
72
            print
73
        
74
        return resp.status, headers, data
75

    
76
    def req(self, method, path, body=None, headers=None, format='text', params=None):
77
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path, format)
78
        if params:
79
            for k,v in params.items():
80
                if v:
81
                    full_path = '%s&%s=%s' %(full_path, k, v)
82
        conn = HTTPConnection(self.host)
83
        
84
        
85
        kwargs = {}
86
        kwargs['headers'] = headers or {}
87
        if not headers or \
88
        'Transfer-Encoding' not in headers \
89
        or headers['Transfer-Encoding'] != 'chunked':
90
            kwargs['headers']['Content-Length'] = len(body) if body else 0
91
        if body:
92
            kwargs['body'] = body
93
            kwargs['headers']['Content-Type'] = 'application/octet-stream'
94
        #print '****', method, full_path, kwargs
95
        #TODO catch socket.error
96
        conn.request(method, full_path, **kwargs)
97
        resp = conn.getresponse()
98
        headers = dict(resp.getheaders())
99
        
100
        if self.verbose:
101
            print '%d %s' % (resp.status, resp.reason)
102
            for key, val in headers.items():
103
                print '%s: %s' % (key.capitalize(), val)
104
            print
105
        
106
        data = resp.read()
107
        if self.debug:
108
            print data
109
            print
110
        
111
        return resp.status, headers, data
112

    
113
    def delete(self, path, format='text'):
114
        return self.req('DELETE', path, format=format)
115

    
116
    def get(self, path, format='text', headers=None, params=None):
117
        return self.req('GET', path, headers=headers, format=format,
118
                        params=params)
119

    
120
    def head(self, path, format='text'):
121
        return self.req('HEAD', path, format=format)
122

    
123
    def post(self, path, body=None, format='text', headers=None):
124
        return self.req('POST', path, body, headers=headers, format=format)
125

    
126
    def put(self, path, body=None, format='text', headers=None):
127
        return self.req('PUT', path, body, headers=headers, format=format)
128

    
129
    def _list(self, path, detail=False, params=None, headers=None):
130
        format = 'json' if detail else 'text'
131
        status, headers, data = self.get(path, format=format, headers=headers,
132
                                         params=params)
133
        if detail:
134
            data = json.loads(data)
135
        else:
136
            data = data.strip().split('\n')
137
        return data
138

    
139
    def _get_metadata(self, path, prefix=None):
140
        status, headers, data = self.head(path)
141
        if status == '404':
142
            return None
143
        prefixlen = prefix and len(prefix) or 0
144
        meta = {}
145
        for key, val in headers.items():
146
            if prefix and not key.startswith(prefix):
147
                continue
148
            elif prefix and key.startswith(prefix):
149
                key = key[prefixlen:]
150
            meta[key] = val
151
        return meta
152

    
153
    def _set_metadata(self, path, entity, **meta):
154
        headers = {}
155
        for key, val in meta.items():
156
            http_key = 'X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())
157
            headers[http_key] = val
158
        self.post(path, headers=headers)
159

    
160
    # Storage Account Services
161

    
162
    def list_containers(self, detail=False, params=None, headers=None):
163
        return self._list('', detail, params, headers)
164

    
165
    def account_metadata(self, restricted=False):
166
        prefix = restricted and 'x-account-meta-' or None
167
        return self._get_metadata('', prefix)
168

    
169
    def update_account_metadata(self, **meta):
170
        self._set_metadata('', 'account', **meta)
171

    
172
    # Storage Container Services
173

    
174
    def list_objects(self, container, detail=False, params=None, headers=None):
175
        return self._list('/' + container, detail, params, headers)
176

    
177
    def create_container(self, container, headers=None):
178
        status, header, data = self.put('/' + container, headers=headers)
179
        if status == 202:
180
            return False
181
        elif status != 201:
182
            raise Fault(data)
183
        return True
184

    
185
    def delete_container(self, container):
186
        self.delete('/' + container)
187

    
188
    def retrieve_container_metadata(self, container, restricted=False):
189
        prefix = restricted and 'x-container-meta-' or None
190
        return self._get_metadata('/%s' % container, prefix)
191

    
192
    def update_container_metadata(self, container, **meta):
193
        self._set_metadata('/' + container, 'container', **meta)
194

    
195
    # Storage Object Services
196

    
197
    def retrieve_object(self, container, object, detail=False, headers=None):
198
        path = '/%s/%s' % (container, object)
199
        format = 'json' if detail else 'text'
200
        status, headers, data = self.get(path, format, headers)
201
        return data
202

    
203
    def create_object(self, container, object, f=stdin, chunked=False,
204
                      blocksize=1024, headers=None):
205
        if not f:
206
            return
207
        path = '/%s/%s' % (container, object)
208
        if not chunked and f != stdin:
209
            data = f.read()
210
            self.put(path, data, headers=headers)
211
        else:
212
            self._chunked_transfer(path, 'PUT', f, headers=headers,
213
                                   blocksize=1024)
214

    
215
    def update_object(self, container, object, f=stdin, chunked=False,
216
                      blocksize=1024, headers=None):
217
        if not f:
218
            return
219
        path = '/%s/%s' % (container, object)
220
        if not chunked and f != stdin:
221
            data = f.read()
222
            self.post(path, data, headers=headers)
223
        else:
224
            self._chunked_transfer(path, 'POST', f, headers=headers,
225
                                   blocksize=1024)
226

    
227
    def _change_obj_location(self, src_container, src_object, dst_container,
228
                             dst_object, remove=False):
229
        path = '/%s/%s' % (dst_container, dst_object)
230
        headers = {}
231
        if remove:
232
            headers['X-Move-From'] = '/%s/%s' % (src_container, src_object)
233
        else:
234
            headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
235
        headers['Content-Length'] = 0
236
        self.put(path, headers=headers)
237

    
238
    def copy_object(self, src_container, src_object, dst_container,
239
                             dst_object):
240
        self._change_obj_location(src_container, src_object,
241
                                   dst_container, dst_object)
242

    
243
    def move_object(self, src_container, src_object, dst_container,
244
                             dst_object):
245
        self._change_obj_location(src_container, src_object,
246
                                   dst_container, dst_object, True)
247

    
248
    def delete_object(self, container, object):
249
        self.delete('/%s/%s' % (container, object))
250

    
251
    def retrieve_object_metadata(self, container, object, restricted=False):
252
        path = '/%s/%s' % (container, object)
253
        prefix = restricted and 'x-object-meta-' or None
254
        return self._get_metadata(path, prefix)
255

    
256
    def update_object_metadata(self, container, object, **meta):
257
        path = '/%s/%s' % (container, object)
258
        self._set_metadata(path, 'object', **meta)