Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ ec1b8d3e

History | View | Annotate | Download (8.6 kB)

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

    
4
import json
5
import types
6

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

    
12

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

    
77
    def req(self, method, path, body=None, headers=None, format='text', params=None):
78
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path, format)
79
        if params:
80
            for k,v in params.items():
81
                if v:
82
                    full_path = '%s&%s=%s' %(full_path, k, v)
83
        conn = HTTPConnection(self.host)
84
        
85
        
86
        kwargs = {}
87
        kwargs['headers'] = headers or {}
88
        if not headers or \
89
        'Transfer-Encoding' not in headers \
90
        or headers['Transfer-Encoding'] != 'chunked':
91
            kwargs['headers']['Content-Length'] = len(body) if body else 0
92
        if body:
93
            kwargs['body'] = body
94
            kwargs['headers']['Content-Type'] = 'application/octet-stream'
95
        #print '****', method, full_path, kwargs
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]
137
        return data
138

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

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

    
158
    # Storage Account Services
159

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

    
163
    def account_metadata(self):
164
        return self._get_metadata('', 'x-account-meta-')
165

    
166
    def update_account_metadata(self, **meta):
167
        self._set_metadata('', 'account', **meta)
168

    
169
    # Storage Container Services
170

    
171
    def list_objects(self, container, detail=False, params=None, headers=None):
172
        return self._list('/' + container, detail, params, headers)
173

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

    
182
    def delete_container(self, container):
183
        self.delete('/' + container)
184

    
185
    def retrieve_container_metadata(self, container):
186
        return self._get_metadata('/%s' % container, 'x-container-meta-')
187

    
188
    def update_container_metadata(self, container, **meta):
189
        self._set_metadata('/' + container, 'container', **meta)
190

    
191
    # Storage Object Services
192

    
193
    def retrieve_object(self, container, object, detail=False, headers=None):
194
        path = '/%s/%s' % (container, object)
195
        format = 'json' if detail else 'text'
196
        status, headers, data = self.get(path, format, headers)
197
        return data
198

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

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

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

    
234
    def copy_object(self, src_container, src_object, dst_container,
235
                             dst_object):
236
        self._change_obj_location(src_container, src_object,
237
                                   dst_container, dst_object)
238

    
239
    def move_object(self, src_container, src_object, dst_container,
240
                             dst_object):
241
        self._change_obj_location(src_container, src_object,
242
                                   dst_container, dst_object, True)
243

    
244
    def delete_object(self, container, object):
245
        self.delete('/%s/%s' % (container, object))
246

    
247
    def retrieve_object_metadata(self, container, object):
248
        path = '/%s/%s' % (container, object)
249
        return self._get_metadata(path, 'x-object-meta-')
250

    
251
    def update_object_metadata(self, container, object, **meta):
252
        path = '/%s/%s' % (container, object)
253
        self._set_metadata(path, 'object', **meta)