Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage.py @ 33487500

History | View | Annotate | Download (10.6 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
        r = self.post(path, success=202)
76
        r.release()
77

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

    
93
    def create_container(self, container):
94
        """
95
        :param container: (str)
96

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

    
106
    def get_container_info(self, container):
107
        """
108
        :param container: (str)
109

110
        :returns: (dict)
111

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

    
122
    def delete_container(self, container):
123
        """
124
        :param container: (str)
125

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

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

    
148
    def upload_object(self, obj, f, size=None):
149
        """ A simple (naive) implementation.
150

151
        :param obj: (str)
152

153
        :param f: an open for reading file descriptor
154

155
        :param size: (int) number of bytes to upload
156
        """
157
        self._assert_container()
158
        path = path4url(self.account, self.container, obj)
159
        data = f.read(size) if size is not None else f.read()
160
        r = self.put(path, data=data, success=201)
161
        r.release()
162

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

    
178
    def create_directory(self, obj):
179
        """
180
        :param obj: (str) directory-object name
181
        """
182
        self._assert_container()
183
        path = path4url(self.account, self.container, obj)
184
        self.set_header('Content-Type', 'application/directory')
185
        self.set_header('Content-length', '0')
186
        r = self.put(path, success=201)
187
        r.release()
188

    
189
    def get_object_info(self, obj):
190
        """
191
        :param obj: (str)
192

193
        :returns: (dict)
194
        """
195
        self._assert_container()
196
        path = path4url(self.account, self.container, obj)
197
        r = self.head(path, success=200)
198
        reply = r.headers
199
        return reply
200

    
201
    def get_object_meta(self, obj):
202
        """
203
        :param obj: (str)
204

205
        :returns: (dict)
206
        """
207
        r = filter_in(self.get_object_info(obj), 'X-Object-Meta-')
208
        reply = {}
209
        for (key, val) in r.items():
210
            metakey = key.split('-')[-1]
211
            reply[metakey] = val
212
        return reply
213

    
214
    def del_object_meta(self, obj, metakey):
215
        """
216
        :param obj: (str)
217

218
        :param metakey: (str) the metadatum key
219
        """
220
        self._assert_container()
221
        self.set_header('X-Object-Meta-' + metakey, '')
222
        path = path4url(self.account, self.container, obj)
223
        r = self.post(path, success=202)
224
        r.release()
225

    
226
    def replace_object_meta(self, metapairs):
227
        """
228
        :param metapairs: (dict) key:val metadata
229
        """
230
        self._assert_container()
231
        path = path4url(self.account, self.container)
232
        for key, val in metapairs.items():
233
            self.set_header('X-Object-Meta-' + key, val)
234
        r = self.post(path, success=202)
235
        r.release()
236

    
237
    def copy_object(
238
            self, src_container, src_object, dst_container,
239
            dst_object=False):
240
        """Copy an objects from src_contaier:src_object to
241
            dst_container[:dst_object]
242

243
        :param src_container: (str)
244

245
        :param src_object: (str)
246

247
        :param dst_container: (str)
248

249
        :param dst_object: (str)
250
        """
251
        self._assert_account()
252
        dst_object = dst_object or src_object
253
        dst_path = path4url(self.account, dst_container, dst_object)
254
        self.set_header('X-Copy-From', path4url(src_container, src_object))
255
        self.set_header('Content-Length', 0)
256
        r = self.put(dst_path, success=201)
257
        r.release()
258

    
259
    def move_object(
260
            self, src_container, src_object, dst_container,
261
            dst_object=False):
262
        """Move an objects from src_contaier:src_object to
263
            dst_container[:dst_object]
264

265
        :param src_container: (str)
266

267
        :param src_object: (str)
268

269
        :param dst_container: (str)
270

271
        :param dst_object: (str)
272
        """
273
        self._assert_account()
274
        dst_object = dst_object or src_object
275
        dst_path = path4url(self.account, dst_container, dst_object)
276
        self.set_header('X-Move-From', path4url(src_container, src_object))
277
        self.set_header('Content-Length', 0)
278
        r = self.put(dst_path, success=201)
279
        r.release()
280

    
281
    def delete_object(self, obj):
282
        """
283
        :param obj: (str)
284

285
        :raises ClientError: 404 Object not found
286
        """
287
        self._assert_container()
288
        path = path4url(self.account, self.container, obj)
289
        r = self.delete(path, success=(204, 404))
290
        if r.status_code == 404:
291
            raise ClientError("Object %s not found" % obj, r.status_code)
292

    
293
    def list_objects(self):
294
        """
295
        :returns: (dict)
296

297
        :raises ClientError: 404 Invalid account
298
        """
299
        self._assert_container()
300
        path = path4url(self.account, self.container)
301
        self.set_param('format', 'json')
302
        r = self.get(path, success=(200, 204, 304, 404), )
303
        if r.status_code == 404:
304
            raise ClientError(
305
                "Invalid account (%s) for that container" % self.account,
306
                r.status_code)
307
        elif r.status_code == 304:
308
            return []
309
        reply = r.json
310
        return reply
311

    
312
    def list_objects_in_path(self, path_prefix):
313
        """
314
        :param path_prefix: (str)
315

316
        :raises ClientError: 404 Invalid account
317

318
        :returns: (dict)
319
        """
320
        self._assert_container()
321
        path = path4url(self.account, self.container)
322
        self.set_param('format', 'json')
323
        self.set_param('path', 'path_prefix')
324
        r = self.get(path, success=(200, 204, 404))
325
        if r.status_code == 404:
326
            raise ClientError(
327
                "Invalid account (%s) for that container" % self.account,
328
                r.status_code)
329
        reply = r.json
330
        return reply