Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / storage.py @ d88ba587

History | View | Annotate | Download (10.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")
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:
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(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
        r = self.post(path, success=202)
90
        r.release()
91

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

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

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

109
        :returns: (dict)
110

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

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

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

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

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

150
        :param obj: (str)
151

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

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

    
162
    def create_directory(self, obj):
163
        """
164
        :param obj: (str) directory-object name
165
        """
166
        self._assert_container()
167
        path = path4url(self.account, self.container, obj)
168
        self.set_header('Content-Type', 'application/directory')
169
        self.set_header('Content-length', '0')
170
        r = self.put(path, success=201)
171
        r.release()
172

    
173
    def get_object_info(self, obj):
174
        """
175
        :param obj: (str)
176

177
        :returns: (dict)
178
        """
179
        self._assert_container()
180
        path = path4url(self.account, self.container, obj)
181
        r = self.head(path, success=200)
182
        reply = r.headers
183
        return reply
184

    
185
    def get_object_meta(self, obj):
186
        """
187
        :param obj: (str)
188

189
        :returns: (dict)
190
        """
191
        r = filter_in(self.get_object_info(obj), 'X-Object-Meta-')
192
        reply = {}
193
        for (key, val) in r.items():
194
            metakey = key.split('-')[-1]
195
            reply[metakey] = val
196
        return reply
197

    
198
    def del_object_meta(self, obj, metakey):
199
        """
200
        :param obj: (str)
201

202
        :param metakey: (str) the metadatum key
203
        """
204
        self._assert_container()
205
        self.set_header('X-Object-Meta-' + metakey, '')
206
        path = path4url(self.account, self.container, obj)
207
        r = self.post(path, success=202)
208
        r.release()
209

    
210
    def replace_object_meta(self, metapairs):
211
        """
212
        :param metapairs: (dict) key:val metadata
213
        """
214
        self._assert_container()
215
        path = path4url(self.account, self.container)
216
        for key, val in metapairs:
217
            self.set_header('X-Object-Meta-' + key, val)
218
        r = self.post(path, success=202)
219
        r.release()
220

    
221
    def get_object(self, obj):
222
        """
223
        :param obj: (str)
224

225
        :returns: (int, int) # of objects, size in bytes
226
        """
227
        self._assert_container()
228
        path = path4url(self.account, self.container, obj)
229
        r = self.get(path, success=200)
230
        size = int(r.headers['content-length'])
231
        cnt = r.content
232
        return cnt, size
233

    
234
    def copy_object(self, src_container, src_object, dst_container,
235
        dst_object=False):
236
        """Copy an objects from src_contaier:src_object to
237
            dst_container[:dst_object]
238

239
        :param src_container: (str)
240

241
        :param src_object: (str)
242

243
        :param dst_container: (str)
244

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

    
255
    def move_object(self, src_container, src_object, dst_container,
256
        dst_object=False):
257
        """Move an objects from src_contaier:src_object to
258
            dst_container[:dst_object]
259

260
        :param src_container: (str)
261

262
        :param src_object: (str)
263

264
        :param dst_container: (str)
265

266
        :param dst_object: (str)
267
        """
268
        self._assert_account()
269
        dst_object = dst_object or src_object
270
        dst_path = path4url(self.account, dst_container, dst_object)
271
        self.set_header('X-Move-From', path4url(src_container, src_object))
272
        self.set_header('Content-Length', 0)
273
        r = self.put(dst_path, success=201)
274
        r.release()
275

    
276
    def delete_object(self, obj):
277
        """
278
        :param obj: (str)
279

280
        :raises ClientError: 404 Object not found
281
        """
282
        self._assert_container()
283
        path = path4url(self.account, self.container, obj)
284
        r = self.delete(path, success=(204, 404))
285
        if r.status_code == 404:
286
            raise ClientError("Object %s not found" % obj, r.status_code)
287

    
288
    def list_objects(self):
289
        """
290
        :returns: (dict)
291

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

    
307
    def list_objects_in_path(self, path_prefix):
308
        """
309
        :param path_prefix: (str)
310

311
        :raises ClientError: 404 Invalid account
312

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