Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ d2d5c360

History | View | Annotate | Download (9.5 kB)

1 ec1b8d3e Sofia Papagiannaki
from httplib import HTTPConnection, HTTP
2 ec1b8d3e Sofia Papagiannaki
from sys import stdin
3 3f5b02c7 Sofia Papagiannaki
4 3f5b02c7 Sofia Papagiannaki
import json
5 ec1b8d3e Sofia Papagiannaki
import types
6 0ea1dcc4 Sofia Papagiannaki
import socket
7 3f5b02c7 Sofia Papagiannaki
8 3f5b02c7 Sofia Papagiannaki
class Fault(Exception):
9 3f5b02c7 Sofia Papagiannaki
    def __init__(self, data=''):
10 3f5b02c7 Sofia Papagiannaki
        Exception.__init__(self, data)
11 3f5b02c7 Sofia Papagiannaki
        self.data = data
12 3f5b02c7 Sofia Papagiannaki
13 3f5b02c7 Sofia Papagiannaki
14 3f5b02c7 Sofia Papagiannaki
class Client(object):
15 3f5b02c7 Sofia Papagiannaki
    def __init__(self, host, account, api='v1', verbose=False, debug=False):
16 3f5b02c7 Sofia Papagiannaki
        """`host` can also include a port, e.g '127.0.0.1:8000'."""
17 3f5b02c7 Sofia Papagiannaki
        
18 3f5b02c7 Sofia Papagiannaki
        self.host = host
19 3f5b02c7 Sofia Papagiannaki
        self.account = account
20 3f5b02c7 Sofia Papagiannaki
        self.api = api
21 3f5b02c7 Sofia Papagiannaki
        self.verbose = verbose or debug
22 3f5b02c7 Sofia Papagiannaki
        self.debug = debug
23 3f5b02c7 Sofia Papagiannaki
    
24 ec1b8d3e Sofia Papagiannaki
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
25 ec1b8d3e Sofia Papagiannaki
                          blocksize=1024):
26 ec1b8d3e Sofia Papagiannaki
        http = HTTPConnection(self.host)
27 ec1b8d3e Sofia Papagiannaki
        
28 ec1b8d3e Sofia Papagiannaki
        # write header
29 ec1b8d3e Sofia Papagiannaki
        path = '/%s/%s%s' % (self.api, self.account, path)
30 ec1b8d3e Sofia Papagiannaki
        http.putrequest(method, path)
31 ec1b8d3e Sofia Papagiannaki
        http.putheader('Content-Type', 'application/octet-stream')
32 ec1b8d3e Sofia Papagiannaki
        http.putheader('Transfer-Encoding', 'chunked')
33 ec1b8d3e Sofia Papagiannaki
        if headers:
34 ec1b8d3e Sofia Papagiannaki
            for header,value in headers.items():
35 ec1b8d3e Sofia Papagiannaki
                http.putheader(header, value)
36 ec1b8d3e Sofia Papagiannaki
        http.endheaders()
37 ec1b8d3e Sofia Papagiannaki
        
38 ec1b8d3e Sofia Papagiannaki
        # write body
39 ec1b8d3e Sofia Papagiannaki
        data = ''
40 ec1b8d3e Sofia Papagiannaki
        while True:
41 ec1b8d3e Sofia Papagiannaki
            if f.closed:
42 ec1b8d3e Sofia Papagiannaki
                break
43 ec1b8d3e Sofia Papagiannaki
            block = f.read(blocksize)
44 ec1b8d3e Sofia Papagiannaki
            if block == '':
45 ec1b8d3e Sofia Papagiannaki
                break
46 ec1b8d3e Sofia Papagiannaki
            data = '%s\r\n%s\r\n' % (hex(len(block)), block)
47 ec1b8d3e Sofia Papagiannaki
            try:
48 ec1b8d3e Sofia Papagiannaki
                http.send(data)
49 ec1b8d3e Sofia Papagiannaki
            except:
50 ec1b8d3e Sofia Papagiannaki
                #retry
51 ec1b8d3e Sofia Papagiannaki
                http.send(data)
52 ec1b8d3e Sofia Papagiannaki
        data = '0x0\r\n'
53 ec1b8d3e Sofia Papagiannaki
        try:
54 ec1b8d3e Sofia Papagiannaki
            http.send(data)
55 ec1b8d3e Sofia Papagiannaki
        except:
56 ec1b8d3e Sofia Papagiannaki
            #retry
57 ec1b8d3e Sofia Papagiannaki
            http.send(data)
58 ec1b8d3e Sofia Papagiannaki
        
59 ec1b8d3e Sofia Papagiannaki
        # get response
60 ec1b8d3e Sofia Papagiannaki
        resp = http.getresponse()
61 ec1b8d3e Sofia Papagiannaki
        headers = dict(resp.getheaders())
62 ec1b8d3e Sofia Papagiannaki
        
63 ec1b8d3e Sofia Papagiannaki
        if self.verbose:
64 ec1b8d3e Sofia Papagiannaki
            print '%d %s' % (resp.status, resp.reason)
65 ec1b8d3e Sofia Papagiannaki
            for key, val in headers.items():
66 ec1b8d3e Sofia Papagiannaki
                print '%s: %s' % (key.capitalize(), val)
67 ec1b8d3e Sofia Papagiannaki
            print
68 ec1b8d3e Sofia Papagiannaki
        
69 ec1b8d3e Sofia Papagiannaki
        data = resp.read()
70 ec1b8d3e Sofia Papagiannaki
        if self.debug:
71 ec1b8d3e Sofia Papagiannaki
            print data
72 ec1b8d3e Sofia Papagiannaki
            print
73 ec1b8d3e Sofia Papagiannaki
        
74 ec1b8d3e Sofia Papagiannaki
        return resp.status, headers, data
75 ec1b8d3e Sofia Papagiannaki
76 d2d5c360 Sofia Papagiannaki
    def req(self, method, path, body=None, headers=None, format='text',
77 d2d5c360 Sofia Papagiannaki
            params=None):
78 d2d5c360 Sofia Papagiannaki
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path,
79 d2d5c360 Sofia Papagiannaki
                                            format)
80 3f5b02c7 Sofia Papagiannaki
        if params:
81 3f5b02c7 Sofia Papagiannaki
            for k,v in params.items():
82 ec1b8d3e Sofia Papagiannaki
                if v:
83 ec1b8d3e Sofia Papagiannaki
                    full_path = '%s&%s=%s' %(full_path, k, v)
84 3f5b02c7 Sofia Papagiannaki
        conn = HTTPConnection(self.host)
85 3f5b02c7 Sofia Papagiannaki
        
86 d2d5c360 Sofia Papagiannaki
        #encode whitespace
87 d2d5c360 Sofia Papagiannaki
        full_path = full_path.replace(' ', '%20')
88 ec1b8d3e Sofia Papagiannaki
        
89 3f5b02c7 Sofia Papagiannaki
        kwargs = {}
90 3f5b02c7 Sofia Papagiannaki
        kwargs['headers'] = headers or {}
91 ec1b8d3e Sofia Papagiannaki
        if not headers or \
92 ec1b8d3e Sofia Papagiannaki
        'Transfer-Encoding' not in headers \
93 ec1b8d3e Sofia Papagiannaki
        or headers['Transfer-Encoding'] != 'chunked':
94 ec1b8d3e Sofia Papagiannaki
            kwargs['headers']['Content-Length'] = len(body) if body else 0
95 3f5b02c7 Sofia Papagiannaki
        if body:
96 3f5b02c7 Sofia Papagiannaki
            kwargs['body'] = body
97 3f5b02c7 Sofia Papagiannaki
            kwargs['headers']['Content-Type'] = 'application/octet-stream'
98 ec1b8d3e Sofia Papagiannaki
        #print '****', method, full_path, kwargs
99 0ea1dcc4 Sofia Papagiannaki
        #TODO catch socket.error
100 ec1b8d3e Sofia Papagiannaki
        conn.request(method, full_path, **kwargs)
101 3f5b02c7 Sofia Papagiannaki
        resp = conn.getresponse()
102 3f5b02c7 Sofia Papagiannaki
        headers = dict(resp.getheaders())
103 3f5b02c7 Sofia Papagiannaki
        
104 3f5b02c7 Sofia Papagiannaki
        if self.verbose:
105 3f5b02c7 Sofia Papagiannaki
            print '%d %s' % (resp.status, resp.reason)
106 3f5b02c7 Sofia Papagiannaki
            for key, val in headers.items():
107 3f5b02c7 Sofia Papagiannaki
                print '%s: %s' % (key.capitalize(), val)
108 3f5b02c7 Sofia Papagiannaki
            print
109 3f5b02c7 Sofia Papagiannaki
        
110 3f5b02c7 Sofia Papagiannaki
        data = resp.read()
111 3f5b02c7 Sofia Papagiannaki
        if self.debug:
112 3f5b02c7 Sofia Papagiannaki
            print data
113 3f5b02c7 Sofia Papagiannaki
            print
114 3f5b02c7 Sofia Papagiannaki
        
115 d2d5c360 Sofia Papagiannaki
        if data:
116 d2d5c360 Sofia Papagiannaki
            assert data[-1] == '\n'
117 d2d5c360 Sofia Papagiannaki
        #remove trailing enter
118 d2d5c360 Sofia Papagiannaki
        data = data and data[:-1] or data
119 d2d5c360 Sofia Papagiannaki
        
120 3f5b02c7 Sofia Papagiannaki
        return resp.status, headers, data
121 ec1b8d3e Sofia Papagiannaki
122 3f5b02c7 Sofia Papagiannaki
    def delete(self, path, format='text'):
123 3f5b02c7 Sofia Papagiannaki
        return self.req('DELETE', path, format=format)
124 ec1b8d3e Sofia Papagiannaki
125 3f5b02c7 Sofia Papagiannaki
    def get(self, path, format='text', headers=None, params=None):
126 3f5b02c7 Sofia Papagiannaki
        return self.req('GET', path, headers=headers, format=format,
127 ec1b8d3e Sofia Papagiannaki
                        params=params)
128 ec1b8d3e Sofia Papagiannaki
129 d2d5c360 Sofia Papagiannaki
    def head(self, path, format='text', params=None):
130 d2d5c360 Sofia Papagiannaki
        return self.req('HEAD', path, format=format, params=params)
131 ec1b8d3e Sofia Papagiannaki
132 3f5b02c7 Sofia Papagiannaki
    def post(self, path, body=None, format='text', headers=None):
133 3f5b02c7 Sofia Papagiannaki
        return self.req('POST', path, body, headers=headers, format=format)
134 ec1b8d3e Sofia Papagiannaki
135 3f5b02c7 Sofia Papagiannaki
    def put(self, path, body=None, format='text', headers=None):
136 3f5b02c7 Sofia Papagiannaki
        return self.req('PUT', path, body, headers=headers, format=format)
137 ec1b8d3e Sofia Papagiannaki
138 3f5b02c7 Sofia Papagiannaki
    def _list(self, path, detail=False, params=None, headers=None):
139 3f5b02c7 Sofia Papagiannaki
        format = 'json' if detail else 'text'
140 3f5b02c7 Sofia Papagiannaki
        status, headers, data = self.get(path, format=format, headers=headers,
141 3f5b02c7 Sofia Papagiannaki
                                         params=params)
142 ec1b8d3e Sofia Papagiannaki
        if detail:
143 ec1b8d3e Sofia Papagiannaki
            data = json.loads(data)
144 ec1b8d3e Sofia Papagiannaki
        else:
145 b41a95a0 Sofia Papagiannaki
            data = data.strip().split('\n')
146 3f5b02c7 Sofia Papagiannaki
        return data
147 ec1b8d3e Sofia Papagiannaki
148 d2d5c360 Sofia Papagiannaki
    def _get_metadata(self, path, prefix=None, params=None):
149 d2d5c360 Sofia Papagiannaki
        status, headers, data = self.head(path, params=params)
150 ec1b8d3e Sofia Papagiannaki
        if status == '404':
151 ec1b8d3e Sofia Papagiannaki
            return None
152 0ea1dcc4 Sofia Papagiannaki
        prefixlen = prefix and len(prefix) or 0
153 3f5b02c7 Sofia Papagiannaki
        meta = {}
154 3f5b02c7 Sofia Papagiannaki
        for key, val in headers.items():
155 0ea1dcc4 Sofia Papagiannaki
            if prefix and not key.startswith(prefix):
156 0ea1dcc4 Sofia Papagiannaki
                continue
157 0ea1dcc4 Sofia Papagiannaki
            elif prefix and key.startswith(prefix):
158 3f5b02c7 Sofia Papagiannaki
                key = key[prefixlen:]
159 0ea1dcc4 Sofia Papagiannaki
            meta[key] = val
160 3f5b02c7 Sofia Papagiannaki
        return meta
161 ec1b8d3e Sofia Papagiannaki
162 3f5b02c7 Sofia Papagiannaki
    def _set_metadata(self, path, entity, **meta):
163 3f5b02c7 Sofia Papagiannaki
        headers = {}
164 3f5b02c7 Sofia Papagiannaki
        for key, val in meta.items():
165 3f5b02c7 Sofia Papagiannaki
            http_key = 'X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())
166 3f5b02c7 Sofia Papagiannaki
            headers[http_key] = val
167 3f5b02c7 Sofia Papagiannaki
        self.post(path, headers=headers)
168 3f5b02c7 Sofia Papagiannaki
169 3f5b02c7 Sofia Papagiannaki
    # Storage Account Services
170 ec1b8d3e Sofia Papagiannaki
171 ec1b8d3e Sofia Papagiannaki
    def list_containers(self, detail=False, params=None, headers=None):
172 3f5b02c7 Sofia Papagiannaki
        return self._list('', detail, params, headers)
173 ec1b8d3e Sofia Papagiannaki
174 d2d5c360 Sofia Papagiannaki
    def account_metadata(self, restricted=False, until=None):
175 0ea1dcc4 Sofia Papagiannaki
        prefix = restricted and 'x-account-meta-' or None
176 d2d5c360 Sofia Papagiannaki
        params = until and {'until':until} or None
177 d2d5c360 Sofia Papagiannaki
        return self._get_metadata('', prefix, params=params)
178 3f5b02c7 Sofia Papagiannaki
179 3f5b02c7 Sofia Papagiannaki
    def update_account_metadata(self, **meta):
180 3f5b02c7 Sofia Papagiannaki
        self._set_metadata('', 'account', **meta)
181 ec1b8d3e Sofia Papagiannaki
182 3f5b02c7 Sofia Papagiannaki
    # Storage Container Services
183 ec1b8d3e Sofia Papagiannaki
184 ec1b8d3e Sofia Papagiannaki
    def list_objects(self, container, detail=False, params=None, headers=None):
185 ec1b8d3e Sofia Papagiannaki
        return self._list('/' + container, detail, params, headers)
186 ec1b8d3e Sofia Papagiannaki
187 ec1b8d3e Sofia Papagiannaki
    def create_container(self, container, headers=None):
188 ec1b8d3e Sofia Papagiannaki
        status, header, data = self.put('/' + container, headers=headers)
189 3f5b02c7 Sofia Papagiannaki
        if status == 202:
190 3f5b02c7 Sofia Papagiannaki
            return False
191 3f5b02c7 Sofia Papagiannaki
        elif status != 201:
192 3f5b02c7 Sofia Papagiannaki
            raise Fault(data)
193 3f5b02c7 Sofia Papagiannaki
        return True
194 ec1b8d3e Sofia Papagiannaki
195 3f5b02c7 Sofia Papagiannaki
    def delete_container(self, container):
196 3f5b02c7 Sofia Papagiannaki
        self.delete('/' + container)
197 ec1b8d3e Sofia Papagiannaki
198 d2d5c360 Sofia Papagiannaki
    def retrieve_container_metadata(self, container, restricted=False,
199 d2d5c360 Sofia Papagiannaki
                                    until=None):
200 0ea1dcc4 Sofia Papagiannaki
        prefix = restricted and 'x-container-meta-' or None
201 d2d5c360 Sofia Papagiannaki
        params = until and {'until':until} or None
202 d2d5c360 Sofia Papagiannaki
        return self._get_metadata('/%s' % container, prefix, params=params)
203 3f5b02c7 Sofia Papagiannaki
204 3f5b02c7 Sofia Papagiannaki
    def update_container_metadata(self, container, **meta):
205 3f5b02c7 Sofia Papagiannaki
        self._set_metadata('/' + container, 'container', **meta)
206 ec1b8d3e Sofia Papagiannaki
207 3f5b02c7 Sofia Papagiannaki
    # Storage Object Services
208 ec1b8d3e Sofia Papagiannaki
209 ec1b8d3e Sofia Papagiannaki
    def retrieve_object(self, container, object, detail=False, headers=None):
210 3f5b02c7 Sofia Papagiannaki
        path = '/%s/%s' % (container, object)
211 ec1b8d3e Sofia Papagiannaki
        format = 'json' if detail else 'text'
212 ec1b8d3e Sofia Papagiannaki
        status, headers, data = self.get(path, format, headers)
213 3f5b02c7 Sofia Papagiannaki
        return data
214 ec1b8d3e Sofia Papagiannaki
215 ec1b8d3e Sofia Papagiannaki
    def create_object(self, container, object, f=stdin, chunked=False,
216 ec1b8d3e Sofia Papagiannaki
                      blocksize=1024, headers=None):
217 d2d5c360 Sofia Papagiannaki
        """
218 d2d5c360 Sofia Papagiannaki
        creates an object
219 d2d5c360 Sofia Papagiannaki
        if f is None then creates a zero length object
220 d2d5c360 Sofia Papagiannaki
        if f is stdin or chunked is set then performs chunked transfer 
221 d2d5c360 Sofia Papagiannaki
        """
222 3f5b02c7 Sofia Papagiannaki
        path = '/%s/%s' % (container, object)
223 ec1b8d3e Sofia Papagiannaki
        if not chunked and f != stdin:
224 d2d5c360 Sofia Papagiannaki
            data = f and f.read() or None
225 ec1b8d3e Sofia Papagiannaki
            self.put(path, data, headers=headers)
226 ec1b8d3e Sofia Papagiannaki
        else:
227 ec1b8d3e Sofia Papagiannaki
            self._chunked_transfer(path, 'PUT', f, headers=headers,
228 ec1b8d3e Sofia Papagiannaki
                                   blocksize=1024)
229 ec1b8d3e Sofia Papagiannaki
230 ec1b8d3e Sofia Papagiannaki
    def update_object(self, container, object, f=stdin, chunked=False,
231 ec1b8d3e Sofia Papagiannaki
                      blocksize=1024, headers=None):
232 ec1b8d3e Sofia Papagiannaki
        if not f:
233 ec1b8d3e Sofia Papagiannaki
            return
234 ec1b8d3e Sofia Papagiannaki
        path = '/%s/%s' % (container, object)
235 ec1b8d3e Sofia Papagiannaki
        if not chunked and f != stdin:
236 ec1b8d3e Sofia Papagiannaki
            data = f.read()
237 ec1b8d3e Sofia Papagiannaki
            self.post(path, data, headers=headers)
238 ec1b8d3e Sofia Papagiannaki
        else:
239 ec1b8d3e Sofia Papagiannaki
            self._chunked_transfer(path, 'POST', f, headers=headers,
240 ec1b8d3e Sofia Papagiannaki
                                   blocksize=1024)
241 ec1b8d3e Sofia Papagiannaki
242 ec1b8d3e Sofia Papagiannaki
    def _change_obj_location(self, src_container, src_object, dst_container,
243 ec1b8d3e Sofia Papagiannaki
                             dst_object, remove=False):
244 3f5b02c7 Sofia Papagiannaki
        path = '/%s/%s' % (dst_container, dst_object)
245 3f5b02c7 Sofia Papagiannaki
        headers = {}
246 ec1b8d3e Sofia Papagiannaki
        if remove:
247 ec1b8d3e Sofia Papagiannaki
            headers['X-Move-From'] = '/%s/%s' % (src_container, src_object)
248 ec1b8d3e Sofia Papagiannaki
        else:
249 ec1b8d3e Sofia Papagiannaki
            headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
250 3f5b02c7 Sofia Papagiannaki
        headers['Content-Length'] = 0
251 3f5b02c7 Sofia Papagiannaki
        self.put(path, headers=headers)
252 ec1b8d3e Sofia Papagiannaki
253 ec1b8d3e Sofia Papagiannaki
    def copy_object(self, src_container, src_object, dst_container,
254 ec1b8d3e Sofia Papagiannaki
                             dst_object):
255 ec1b8d3e Sofia Papagiannaki
        self._change_obj_location(src_container, src_object,
256 ec1b8d3e Sofia Papagiannaki
                                   dst_container, dst_object)
257 ec1b8d3e Sofia Papagiannaki
258 ec1b8d3e Sofia Papagiannaki
    def move_object(self, src_container, src_object, dst_container,
259 ec1b8d3e Sofia Papagiannaki
                             dst_object):
260 ec1b8d3e Sofia Papagiannaki
        self._change_obj_location(src_container, src_object,
261 ec1b8d3e Sofia Papagiannaki
                                   dst_container, dst_object, True)
262 ec1b8d3e Sofia Papagiannaki
263 3f5b02c7 Sofia Papagiannaki
    def delete_object(self, container, object):
264 3f5b02c7 Sofia Papagiannaki
        self.delete('/%s/%s' % (container, object))
265 ec1b8d3e Sofia Papagiannaki
266 0ea1dcc4 Sofia Papagiannaki
    def retrieve_object_metadata(self, container, object, restricted=False):
267 3f5b02c7 Sofia Papagiannaki
        path = '/%s/%s' % (container, object)
268 0ea1dcc4 Sofia Papagiannaki
        prefix = restricted and 'x-object-meta-' or None
269 0ea1dcc4 Sofia Papagiannaki
        return self._get_metadata(path, prefix)
270 ec1b8d3e Sofia Papagiannaki
271 3f5b02c7 Sofia Papagiannaki
    def update_object_metadata(self, container, object, **meta):
272 3f5b02c7 Sofia Papagiannaki
        path = '/%s/%s' % (container, object)
273 3f5b02c7 Sofia Papagiannaki
        self._set_metadata(path, 'object', **meta)