Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / api / quotas.py @ 2aba7764

History | View | Annotate | Download (10.2 kB)

1
# Copyright 2013 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 django.utils import simplejson as json
35
from django.views.decorators.csrf import csrf_exempt
36
from django.http import HttpResponse
37
from django.db import transaction
38

    
39
from snf_django.lib import api
40
from snf_django.lib.api.faults import BadRequest, ItemNotFound
41
from django.core.cache import cache
42

    
43
from astakos.im import settings
44
from astakos.im import register
45
from astakos.im.quotas import get_user_quotas, service_get_quotas
46

    
47
import astakos.quotaholder_app.exception as qh_exception
48
import astakos.quotaholder_app.callpoint as qh
49

    
50
from .util import (json_response, is_integer, are_integer,
51
                   user_from_token, component_from_token)
52

    
53

    
54
def get_visible_resources():
55
    key = "resources"
56
    result = cache.get(key)
57
    if result is None:
58
        result = register.get_api_visible_resources()
59
        cache.set(key, result, settings.RESOURCE_CACHE_TIMEOUT)
60
    return result
61

    
62

    
63
@api.api_method(http_method='GET', token_required=True, user_required=False)
64
@user_from_token
65
def quotas(request):
66
    visible_resources = get_visible_resources()
67
    resource_names = [r.name for r in visible_resources]
68
    result = get_user_quotas(request.user, resources=resource_names)
69
    return json_response(result)
70

    
71

    
72
@api.api_method(http_method='GET', token_required=True, user_required=False)
73
@component_from_token
74
def service_quotas(request):
75
    user = request.GET.get('user')
76
    users = [user] if user is not None else None
77
    result = service_get_quotas(request.component_instance, users=users)
78

    
79
    if user is not None and result == {}:
80
        raise ItemNotFound("No such user '%s'" % user)
81

    
82
    return json_response(result)
83

    
84

    
85
@api.api_method(http_method='GET', token_required=False, user_required=False)
86
def resources(request):
87
    resources = get_visible_resources()
88
    result = register.resources_to_dict(resources)
89
    return json_response(result)
90

    
91

    
92
@csrf_exempt
93
def commissions(request):
94
    method = request.method
95
    if method == 'GET':
96
        return get_pending_commissions(request)
97
    elif method == 'POST':
98
        return issue_commission(request)
99
    return api.api_method_not_allowed(request, allowed_methods=['GET', 'POST'])
100

    
101

    
102
@api.api_method(http_method='GET', token_required=True, user_required=False)
103
@component_from_token
104
def get_pending_commissions(request):
105
    client_key = str(request.component_instance)
106

    
107
    result = qh.get_pending_commissions(clientkey=client_key)
108
    return json_response(result)
109

    
110

    
111
def _provisions_to_list(provisions):
112
    lst = []
113
    for provision in provisions:
114
        try:
115
            holder = provision['holder']
116
            source = provision['source']
117
            resource = provision['resource']
118
            quantity = provision['quantity']
119
            key = (holder, source, resource)
120
            lst.append((key, quantity))
121
            if not is_integer(quantity):
122
                raise ValueError()
123
        except (TypeError, KeyError, ValueError):
124
            raise BadRequest("Malformed provision %s" % str(provision))
125
    return lst
126

    
127

    
128
@csrf_exempt
129
@api.api_method(http_method='POST', token_required=True, user_required=False)
130
@component_from_token
131
def issue_commission(request):
132
    data = request.body
133
    try:
134
        input_data = json.loads(data)
135
    except json.JSONDecodeError:
136
        raise BadRequest("POST data should be in json format.")
137

    
138
    client_key = str(request.component_instance)
139
    provisions = input_data.get('provisions')
140
    if provisions is None:
141
        raise BadRequest("Provisions are missing.")
142
    if not isinstance(provisions, list):
143
        raise BadRequest("Provisions should be a list.")
144

    
145
    provisions = _provisions_to_list(provisions)
146
    force = input_data.get('force', False)
147
    if not isinstance(force, bool):
148
        raise BadRequest('"force" option should be a boolean.')
149

    
150
    auto_accept = input_data.get('auto_accept', False)
151
    if not isinstance(auto_accept, bool):
152
        raise BadRequest('"auto_accept" option should be a boolean.')
153

    
154
    name = input_data.get('name', "")
155
    if not isinstance(name, basestring):
156
        raise BadRequest("Commission name should be a string.")
157

    
158
    try:
159
        result = _issue_commission(clientkey=client_key,
160
                                   provisions=provisions,
161
                                   name=name,
162
                                   force=force,
163
                                   accept=auto_accept)
164
        data = {"serial": result}
165
        status_code = 201
166
    except (qh_exception.NoCapacityError,
167
            qh_exception.NoQuantityError) as e:
168
        status_code = 413
169
        body = {"message": e.message,
170
                "code": status_code,
171
                "data": e.data,
172
                }
173
        data = {"overLimit": body}
174
    except qh_exception.NoHoldingError as e:
175
        status_code = 404
176
        body = {"message": e.message,
177
                "code": status_code,
178
                "data": e.data,
179
                }
180
        data = {"itemNotFound": body}
181
    except qh_exception.InvalidDataError as e:
182
        status_code = 400
183
        body = {"message": e.message,
184
                "code": status_code,
185
                }
186
        data = {"badRequest": body}
187

    
188
    return json_response(data, status_code=status_code)
189

    
190

    
191
@transaction.commit_on_success
192
def _issue_commission(clientkey, provisions, name, force, accept):
193
    serial = qh.issue_commission(clientkey=clientkey,
194
                                 provisions=provisions,
195
                                 name=name,
196
                                 force=force)
197
    if accept:
198
        qh.resolve_pending_commission(clientkey=clientkey, serial=serial)
199

    
200
    return serial
201

    
202

    
203
def notFoundCF(serial):
204
    body = {"code": 404,
205
            "message": "serial %s does not exist" % serial,
206
            }
207
    return {"itemNotFound": body}
208

    
209

    
210
def conflictingCF(serial):
211
    body = {"code": 400,
212
            "message": "cannot both accept and reject serial %s" % serial,
213
            }
214
    return {"badRequest": body}
215

    
216

    
217
@csrf_exempt
218
@api.api_method(http_method='POST', token_required=True, user_required=False)
219
@component_from_token
220
@transaction.commit_on_success
221
def resolve_pending_commissions(request):
222
    data = request.body
223
    try:
224
        input_data = json.loads(data)
225
    except json.JSONDecodeError:
226
        raise BadRequest("POST data should be in json format.")
227

    
228
    client_key = str(request.component_instance)
229
    accept = input_data.get('accept', [])
230
    reject = input_data.get('reject', [])
231

    
232
    if not isinstance(accept, list) or not isinstance(reject, list):
233
        m = '"accept" and "reject" should reference lists of serials.'
234
        raise BadRequest(m)
235

    
236
    if not are_integer(accept) or not are_integer(reject):
237
        raise BadRequest("Serials should be integer.")
238

    
239
    result = qh.resolve_pending_commissions(clientkey=client_key,
240
                                            accept_set=accept,
241
                                            reject_set=reject)
242
    accepted, rejected, notFound, conflicting = result
243
    notFound = [(serial, notFoundCF(serial)) for serial in notFound]
244
    conflicting = [(serial, conflictingCF(serial)) for serial in conflicting]
245
    cloudfaults = notFound + conflicting
246
    data = {'accepted': accepted,
247
            'rejected': rejected,
248
            'failed': cloudfaults
249
            }
250

    
251
    return json_response(data)
252

    
253

    
254
@api.api_method(http_method='GET', token_required=True, user_required=False)
255
@component_from_token
256
def get_commission(request, serial):
257
    data = request.GET
258
    client_key = str(request.component_instance)
259
    try:
260
        serial = int(serial)
261
    except ValueError:
262
        raise BadRequest("Serial should be an integer.")
263

    
264
    try:
265
        data = qh.get_commission(clientkey=client_key,
266
                                 serial=serial)
267
        status_code = 200
268
        return json_response(data, status_code)
269
    except qh_exception.NoCommissionError:
270
        return HttpResponse(status=404)
271

    
272

    
273
@csrf_exempt
274
@api.api_method(http_method='POST', token_required=True, user_required=False)
275
@component_from_token
276
@transaction.commit_on_success
277
def serial_action(request, serial):
278
    data = request.body
279
    try:
280
        input_data = json.loads(data)
281
    except json.JSONDecodeError:
282
        raise BadRequest("POST data should be in json format.")
283

    
284
    try:
285
        serial = int(serial)
286
    except ValueError:
287
        raise BadRequest("Serial should be an integer.")
288

    
289
    client_key = str(request.component_instance)
290

    
291
    accept = 'accept' in input_data
292
    reject = 'reject' in input_data
293

    
294
    if accept == reject:
295
        raise BadRequest('Specify either accept or reject action.')
296

    
297
    result = qh.resolve_pending_commission(clientkey=client_key,
298
                                           serial=serial,
299
                                           accept=accept)
300
    response = HttpResponse()
301
    if not result:
302
        response.status_code = 404
303

    
304
    return response