Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / api / images.py @ cda71050

History | View | Annotate | Download (11.7 kB)

1
# Copyright 2011-2012 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 logging import getLogger
35

    
36
from dateutil.parser import parse as date_parse
37

    
38
from django.conf.urls.defaults import patterns
39
from django.http import HttpResponse
40
from django.template.loader import render_to_string
41
from django.utils import simplejson as json
42

    
43
from snf_django.lib import api
44
from snf_django.lib.api import faults, utils
45
from synnefo.api import util
46
from synnefo.plankton.utils import image_backend
47

    
48

    
49
log = getLogger(__name__)
50

    
51
urlpatterns = patterns(
52
    'synnefo.api.images',
53
    (r'^(?:/|.json|.xml)?$', 'demux'),
54
    (r'^/detail(?:.json|.xml)?$', 'list_images', {'detail': True}),
55
    (r'^/([\w-]+)(?:.json|.xml)?$', 'image_demux'),
56
    (r'^/([\w-]+)/meta(?:.json|.xml)?$', 'metadata_demux'),
57
    (r'^/([\w-]+)/meta/(.+?)(?:.json|.xml)?$', 'metadata_item_demux')
58
)
59

    
60

    
61
def demux(request):
62
    if request.method == 'GET':
63
        return list_images(request)
64
    elif request.method == 'POST':
65
        return create_image(request)
66
    else:
67
        return api.method_not_allowed(request)
68

    
69

    
70
def image_demux(request, image_id):
71
    if request.method == 'GET':
72
        return get_image_details(request, image_id)
73
    elif request.method == 'DELETE':
74
        return delete_image(request, image_id)
75
    else:
76
        return api.method_not_allowed(request)
77

    
78

    
79
def metadata_demux(request, image_id):
80
    if request.method == 'GET':
81
        return list_metadata(request, image_id)
82
    elif request.method == 'POST':
83
        return update_metadata(request, image_id)
84
    else:
85
        return api.method_not_allowed(request)
86

    
87

    
88
def metadata_item_demux(request, image_id, key):
89
    if request.method == 'GET':
90
        return get_metadata_item(request, image_id, key)
91
    elif request.method == 'PUT':
92
        return create_metadata_item(request, image_id, key)
93
    elif request.method == 'DELETE':
94
        return delete_metadata_item(request, image_id, key)
95
    else:
96
        return api.method_not_allowed(request)
97

    
98

    
99
def image_to_dict(image, detail=True):
100
    d = dict(id=image['id'], name=image['name'])
101
    if detail:
102
        d['updated'] = utils.isoformat(date_parse(image['updated_at']))
103
        d['created'] = utils.isoformat(date_parse(image['created_at']))
104
        d['status'] = 'DELETED' if image['deleted_at'] else 'ACTIVE'
105
        d['progress'] = 100 if image['status'] == 'available' else 0
106
        if image['properties']:
107
            d['metadata'] = {'values': image['properties']}
108
    return d
109

    
110

    
111
@api.api_method("GET", user_required=True, logger=log)
112
def list_images(request, detail=False):
113
    # Normal Response Codes: 200, 203
114
    # Error Response Codes: computeFault (400, 500),
115
    #                       serviceUnavailable (503),
116
    #                       unauthorized (401),
117
    #                       badRequest (400),
118
    #                       overLimit (413)
119

    
120
    log.debug('list_images detail=%s', detail)
121
    since = utils.isoparse(request.GET.get('changes-since'))
122
    with image_backend(request.user_uniq) as backend:
123
        if since:
124
            images = []
125
            for image in backend.iter():
126
                updated = date_parse(image['updated_at'])
127
                if updated >= since:
128
                    images.append(image)
129
            if not images:
130
                return HttpResponse(status=304)
131
        else:
132
            images = backend.list()
133

    
134
    images = sorted(images, key=lambda x: x['id'])
135
    reply = [image_to_dict(image, detail) for image in images]
136

    
137
    if request.serialization == 'xml':
138
        data = render_to_string('list_images.xml',
139
                                dict(images=reply, detail=detail))
140
    else:
141
        data = json.dumps(dict(images={'values': reply}))
142

    
143
    return HttpResponse(data, status=200)
144

    
145

    
146
@api.api_method('POST', user_required=True, logger=log)
147
def create_image(request):
148
    # Normal Response Code: 202
149
    # Error Response Codes: computeFault (400, 500),
150
    #                       serviceUnavailable (503),
151
    #                       unauthorized (401),
152
    #                       badMediaType(415),
153
    #                       itemNotFound (404),
154
    #                       badRequest (400),
155
    #                       serverCapacityUnavailable (503),
156
    #                       buildInProgress (409),
157
    #                       resizeNotAllowed (403),
158
    #                       backupOrResizeInProgress (409),
159
    #                       overLimit (413)
160

    
161
    raise faults.NotImplemented('Not supported.')
162

    
163

    
164
@api.api_method('GET', user_required=True, logger=log)
165
def get_image_details(request, image_id):
166
    # Normal Response Codes: 200, 203
167
    # Error Response Codes: computeFault (400, 500),
168
    #                       serviceUnavailable (503),
169
    #                       unauthorized (401),
170
    #                       badRequest (400),
171
    #                       itemNotFound (404),
172
    #                       overLimit (413)
173

    
174
    log.debug('get_image_details %s', image_id)
175
    with image_backend(request.user_uniq) as backend:
176
        image = backend.get_image(image_id)
177
    reply = image_to_dict(image)
178

    
179
    if request.serialization == 'xml':
180
        data = render_to_string('image.xml', dict(image=reply))
181
    else:
182
        data = json.dumps(dict(image=reply))
183

    
184
    return HttpResponse(data, status=200)
185

    
186

    
187
@api.api_method('DELETE', user_required=True, logger=log)
188
def delete_image(request, image_id):
189
    # Normal Response Code: 204
190
    # Error Response Codes: computeFault (400, 500),
191
    #                       serviceUnavailable (503),
192
    #                       unauthorized (401),
193
    #                       itemNotFound (404),
194
    #                       overLimit (413)
195

    
196
    log.info('delete_image %s', image_id)
197
    with image_backend(request.user_uniq) as backend:
198
        backend.unregister(image_id)
199
    log.info('User %s deleted image %s', request.user_uniq, image_id)
200
    return HttpResponse(status=204)
201

    
202

    
203
@api.api_method('GET', user_required=True, logger=log)
204
def list_metadata(request, image_id):
205
    # Normal Response Codes: 200, 203
206
    # Error Response Codes: computeFault (400, 500),
207
    #                       serviceUnavailable (503),
208
    #                       unauthorized (401),
209
    #                       badRequest (400),
210
    #                       overLimit (413)
211

    
212
    log.debug('list_image_metadata %s', image_id)
213
    with image_backend(request.user_uniq) as backend:
214
        image = backend.get_image(image_id)
215
    metadata = image['properties']
216
    return util.render_metadata(request, metadata, use_values=True, status=200)
217

    
218

    
219
@api.api_method('POST', user_required=True, logger=log)
220
def update_metadata(request, image_id):
221
    # Normal Response Code: 201
222
    # Error Response Codes: computeFault (400, 500),
223
    #                       serviceUnavailable (503),
224
    #                       unauthorized (401),
225
    #                       badRequest (400),
226
    #                       buildInProgress (409),
227
    #                       badMediaType(415),
228
    #                       overLimit (413)
229

    
230
    req = utils.get_request_dict(request)
231
    log.info('update_image_metadata %s %s', image_id, req)
232
    with image_backend(request.user_uniq) as backend:
233
        image = backend.get_image(image_id)
234
        try:
235
            metadata = req['metadata']
236
            assert isinstance(metadata, dict)
237
        except (KeyError, AssertionError):
238
            raise faults.BadRequest('Malformed request.')
239

    
240
        properties = image['properties']
241
        properties.update(metadata)
242

    
243
        backend.update_metadata(image_id, dict(properties=properties))
244

    
245
    return util.render_metadata(request, properties, status=201)
246

    
247

    
248
@api.api_method('GET', user_required=True, logger=log)
249
def get_metadata_item(request, image_id, key):
250
    # Normal Response Codes: 200, 203
251
    # Error Response Codes: computeFault (400, 500),
252
    #                       serviceUnavailable (503),
253
    #                       unauthorized (401),
254
    #                       itemNotFound (404),
255
    #                       badRequest (400),
256
    #                       overLimit (413)
257

    
258
    log.debug('get_image_metadata_item %s %s', image_id, key)
259
    with image_backend(request.user_uniq) as backend:
260
        image = backend.get_image(image_id)
261
    val = image['properties'].get(key)
262
    if val is None:
263
        raise faults.ItemNotFound('Metadata key not found.')
264
    return util.render_meta(request, {key: val}, status=200)
265

    
266

    
267
@api.api_method('PUT', user_required=True, logger=log)
268
def create_metadata_item(request, image_id, key):
269
    # Normal Response Code: 201
270
    # Error Response Codes: computeFault (400, 500),
271
    #                       serviceUnavailable (503),
272
    #                       unauthorized (401),
273
    #                       itemNotFound (404),
274
    #                       badRequest (400),
275
    #                       buildInProgress (409),
276
    #                       badMediaType(415),
277
    #                       overLimit (413)
278

    
279
    req = utils.get_request_dict(request)
280
    log.info('create_image_metadata_item %s %s %s', image_id, key, req)
281
    try:
282
        metadict = req['meta']
283
        assert isinstance(metadict, dict)
284
        assert len(metadict) == 1
285
        assert key in metadict
286
    except (KeyError, AssertionError):
287
        raise faults.BadRequest('Malformed request.')
288

    
289
    val = metadict[key]
290
    with image_backend(request.user_uniq) as backend:
291
        image = backend.get_image(image_id)
292
        properties = image['properties']
293
        properties[key] = val
294

    
295
        backend.update_metadata(image_id, dict(properties=properties))
296

    
297
    return util.render_meta(request, {key: val}, status=201)
298

    
299

    
300
@api.api_method('DELETE', user_required=True, logger=log)
301
def delete_metadata_item(request, image_id, key):
302
    # Normal Response Code: 204
303
    # Error Response Codes: computeFault (400, 500),
304
    #                       serviceUnavailable (503),
305
    #                       unauthorized (401),
306
    #                       itemNotFound (404),
307
    #                       badRequest (400),
308
    #                       buildInProgress (409),
309
    #                       badMediaType(415),
310
    #                       overLimit (413),
311

    
312
    log.info('delete_image_metadata_item %s %s', image_id, key)
313
    with image_backend(request.user_uniq) as backend:
314
        image = backend.get_image(image_id)
315
        properties = image['properties']
316
        properties.pop(key, None)
317

    
318
        backend.update_metadata(image_id, dict(properties=properties))
319

    
320
    return HttpResponse(status=204)