Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage / __init__.py @ 3f7e4e14

History | View | Annotate | Download (11.3 kB)

1
#a Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from kamaki.clients import Client, ClientError
35
from kamaki.clients.utils import filter_in, filter_out, path4url
36

    
37

    
38
class StorageClient(Client):
39
    """OpenStack Object Storage API 1.0 client"""
40

    
41
    def __init__(self, base_url, token, account=None, container=None):
42
        super(StorageClient, self).__init__(base_url, token)
43
        self.account = account
44
        self.container = container
45

    
46
    def _assert_account(self):
47
        if not self.account:
48
            raise ClientError("No account provided")
49

    
50
    def _assert_container(self):
51
        self._assert_account()
52
        if not self.container:
53
            raise ClientError("No container provided")
54

    
55
    def get_account_info(self):
56
        """
57
        :returns: (dict)
58
        """
59
        self._assert_account()
60
        path = path4url(self.account)
61
        r = self.head(path, success=(204, 401))
62
        if r.status_code == 401:
63
            raise ClientError("No authorization", status=401)
64
        reply = r.headers
65
        return reply
66

    
67
    def replace_account_meta(self, metapairs):
68
        """
69
        :param metapais: (dict) key:val metadata pairs
70
        """
71
        self._assert_account()
72
        path = path4url(self.account)
73
        for key, val in metapairs.items():
74
            self.set_header('X-Account-Meta-' + key, val)
75
        self.post(path, success=202)
76

    
77
    def del_account_meta(self, metakey):
78
        """
79
        :param metakey: (str) metadatum key
80
        """
81
        headers = self.get_account_info()
82
        self.headers = filter_out(
83
            headers,
84
            'X-Account-Meta-' + metakey,
85
            exactMatch=True)
86
        if len(self.headers) == len(headers):
87
            raise ClientError('X-Account-Meta-%s not found' % metakey, 404)
88
        path = path4url(self.account)
89
        self.post(path, success=202)
90

    
91
    def create_container(self, container):
92
        """
93
        :param container: (str)
94

95
        :raises ClientError: 202 Container already exists
96
        """
97
        self._assert_account()
98
        path = path4url(self.account, container)
99
        r = self.put(path, success=(201, 202))
100
        if r.status_code == 202:
101
            raise ClientError("Container already exists", r.status_code)
102

    
103
    def get_container_info(self, container):
104
        """
105
        :param container: (str)
106

107
        :returns: (dict)
108

109
        :raises ClientError: 404 Container does not exist
110
        """
111
        self._assert_account()
112
        path = path4url(self.account, container)
113
        r = self.head(path, success=(204, 404))
114
        if r.status_code == 404:
115
            raise ClientError("Container does not exist", r.status_code)
116
        reply = r.headers
117
        return reply
118

    
119
    def delete_container(self, container):
120
        """
121
        :param container: (str)
122

123
        :raises ClientError: 404 Container does not exist
124
        :raises ClientError: 409 Container not empty
125
        """
126
        self._assert_account()
127
        path = path4url(self.account, container)
128
        r = self.delete(path, success=(204, 404, 409))
129
        if r.status_code == 404:
130
            raise ClientError("Container does not exist", r.status_code)
131
        elif r.status_code == 409:
132
            raise ClientError("Container is not empty", r.status_code)
133

    
134
    def list_containers(self):
135
        """
136
        :returns: (dict)
137
        """
138
        self._assert_account()
139
        self.set_param('format', 'json')
140
        path = path4url(self.account)
141
        r = self.get(path, success=(200, 204))
142
        reply = r.json
143
        return reply
144

    
145
    def upload_object(self, obj, f, size=None):
146
        """ A simple (naive) implementation.
147

148
        :param obj: (str)
149

150
        :param f: an open for reading file descriptor
151

152
        :param size: (int) number of bytes to upload
153
        """
154
        self._assert_container()
155
        path = path4url(self.account, self.container, obj)
156
        data = f.read(size) if size else f.read()
157
        self.put(path, data=data, success=201)
158

    
159
    def create_object(
160
            self, obj,
161
            content_type='application/octet-stream', content_length=0):
162
        """
163
        :param obj: (str) directory-object name
164
        :param content_type: (str) explicitly set content_type
165
        :param content_length: (int) explicitly set content length
166
        """
167
        self._assert_container()
168
        path = path4url(self.account, self.container, obj)
169
        self.set_header('Content-Type', content_type)
170
        self.set_header('Content-length', str(content_length))
171
        self.put(path, success=201)
172

    
173
    def create_directory(self, obj):
174
        """
175
        :param obj: (str) directory-object name
176
        """
177
        self._assert_container()
178
        path = path4url(self.account, self.container, obj)
179
        self.set_header('Content-Type', 'application/directory')
180
        self.set_header('Content-length', '0')
181
        self.put(path, success=201)
182

    
183
    def get_object_info(self, obj):
184
        """
185
        :param obj: (str)
186

187
        :returns: (dict)
188
        """
189
        self._assert_container()
190
        path = path4url(self.account, self.container, obj)
191
        r = self.head(path, success=200)
192
        reply = r.headers
193
        return reply
194

    
195
    def get_object_meta(self, obj):
196
        """
197
        :param obj: (str)
198

199
        :returns: (dict)
200
        """
201
        r = filter_in(self.get_object_info(obj), 'X-Object-Meta-')
202
        reply = {}
203
        for (key, val) in r.items():
204
            metakey = key.split('-')[-1]
205
            reply[metakey] = val
206
        return reply
207

    
208
    def del_object_meta(self, obj, metakey):
209
        """
210
        :param obj: (str)
211

212
        :param metakey: (str) the metadatum key
213
        """
214
        self._assert_container()
215
        self.set_header('X-Object-Meta-' + metakey, '')
216
        path = path4url(self.account, self.container, obj)
217
        self.post(path, success=202)
218

    
219
    def replace_object_meta(self, metapairs):
220
        """
221
        :param metapairs: (dict) key:val metadata
222
        """
223
        self._assert_container()
224
        path = path4url(self.account, self.container)
225
        for key, val in metapairs.items():
226
            self.set_header('X-Object-Meta-' + key, val)
227
        self.post(path, success=202)
228

    
229
    def copy_object(
230
            self, src_container, src_object, dst_container,
231
            dst_object=False):
232
        """Copy an objects from src_contaier:src_object to
233
            dst_container[:dst_object]
234

235
        :param src_container: (str)
236

237
        :param src_object: (str)
238

239
        :param dst_container: (str)
240

241
        :param dst_object: (str)
242
        """
243
        self._assert_account()
244
        dst_object = dst_object or src_object
245
        dst_path = path4url(self.account, dst_container, dst_object)
246
        self.set_header('X-Copy-From', path4url(src_container, src_object))
247
        self.set_header('Content-Length', 0)
248
        self.put(dst_path, success=201)
249

    
250
    def move_object(
251
            self, src_container, src_object, dst_container,
252
            dst_object=False):
253
        """Move an objects from src_contaier:src_object to
254
            dst_container[:dst_object]
255

256
        :param src_container: (str)
257

258
        :param src_object: (str)
259

260
        :param dst_container: (str)
261

262
        :param dst_object: (str)
263
        """
264
        self._assert_account()
265
        dst_object = dst_object or src_object
266
        dst_path = path4url(self.account, dst_container, dst_object)
267
        self.set_header('X-Move-From', path4url(src_container, src_object))
268
        self.set_header('Content-Length', 0)
269
        self.put(dst_path, success=201)
270

    
271
    def delete_object(self, obj):
272
        """
273
        :param obj: (str)
274

275
        :raises ClientError: 404 Object not found
276
        """
277
        self._assert_container()
278
        path = path4url(self.account, self.container, obj)
279
        r = self.delete(path, success=(204, 404))
280
        if r.status_code == 404:
281
            raise ClientError("Object %s not found" % obj, r.status_code)
282

    
283
    def list_objects(
284
            self,
285
            limit=None,
286
            marker=None,
287
            prefix=None,
288
            format=None,
289
            delimiter=None,
290
            path=None):
291
        """
292
        :param limit: (integer) The amount of results requested
293

294
        :param marker: (string) Return containers with name lexicographically
295
            after marker
296

297
        :param prefix: (string) Return objects starting with prefix
298

299
        :param format: (string) reply format can be json or xml (default:json)
300

301
        :param delimiter: (string) Return objects up to the delimiter
302

303
        :param path: (string) assume prefix = path and delimiter = /
304
            (overwrites prefix and delimiter)
305

306
        :returns: (dict)
307

308
        :raises ClientError: 404 Invalid account
309
        """
310
        self._assert_container()
311
        restpath = path4url(self.account, self.container)
312

    
313
        self.set_param('format', format or 'json')
314

    
315
        self.set_param('limit', limit, iff=limit)
316
        self.set_param('marker', marker, iff=marker)
317
        if path:
318
            self.set_param('path', path)
319
        else:
320
            self.set_param('prefix', prefix, iff=prefix)
321
            self.set_param('delimiter', delimiter, iff=delimiter)
322

    
323
        r = self.get(restpath, success=(200, 204, 304, 404), )
324
        if r.status_code == 404:
325
            raise ClientError(
326
                "Invalid account (%s) for that container" % self.account,
327
                r.status_code)
328
        elif r.status_code == 304:
329
            return []
330
        return r.json
331

    
332
    def list_objects_in_path(self, path_prefix):
333
        """
334
        :param path_prefix: (str)
335

336
        :raises ClientError: 404 Invalid account
337

338
        :returns: (dict)
339
        """
340
        self._assert_container()
341
        path = path4url(self.account, self.container)
342
        self.set_param('format', 'json')
343
        self.set_param('path', path_prefix)
344
        r = self.get(path, success=(200, 204, 404))
345
        if r.status_code == 404:
346
            raise ClientError(
347
                "Invalid account (%s) for that container" % self.account,
348
                r.status_code)
349
        reply = r.json
350
        return reply