Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / api / quotas.py @ ff5edb80

History | View | Annotate | Download (10.9 kB)

1
# Copyright 2013-2014 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
    service_get_project_quotas, project_ref
47

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

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

    
54

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

    
63

    
64
@api.api_method(http_method='GET', token_required=True, user_required=False)
65
@user_from_token
66
def quotas(request):
67
    visible_resources = get_visible_resources()
68
    resource_names = [r.name for r in visible_resources]
69
    memberships = request.user.projectmembership_set.actually_accepted()
70
    sources = [project_ref(m.project.uuid) for m in memberships]
71
    result = get_user_quotas(request.user, resources=resource_names,
72
                             sources=sources)
73
    return json_response(result)
74

    
75

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

    
83
    if user is not None and result == {}:
84
        raise ItemNotFound("No such user '%s'" % user)
85

    
86
    return json_response(result)
87

    
88

    
89
@api.api_method(http_method='GET', token_required=True, user_required=False)
90
@component_from_token
91
def service_project_quotas(request):
92
    project = request.GET.get('project')
93
    projects = [project] if project is not None else None
94
    result = service_get_project_quotas(request.component_instance,
95
                                        projects=projects)
96

    
97
    if project is not None and result == {}:
98
        raise ItemNotFound("No such project '%s'" % project)
99

    
100
    return json_response(result)
101

    
102

    
103
@api.api_method(http_method='GET', token_required=False, user_required=False)
104
def resources(request):
105
    resources = get_visible_resources()
106
    result = register.resources_to_dict(resources)
107
    return json_response(result)
108

    
109

    
110
@csrf_exempt
111
def commissions(request):
112
    method = request.method
113
    if method == 'GET':
114
        return get_pending_commissions(request)
115
    elif method == 'POST':
116
        return issue_commission(request)
117
    return api.api_method_not_allowed(request, allowed_methods=['GET', 'POST'])
118

    
119

    
120
@api.api_method(http_method='GET', token_required=True, user_required=False)
121
@component_from_token
122
def get_pending_commissions(request):
123
    client_key = str(request.component_instance)
124

    
125
    result = qh.get_pending_commissions(clientkey=client_key)
126
    return json_response(result)
127

    
128

    
129
def _provisions_to_list(provisions):
130
    lst = []
131
    for provision in provisions:
132
        try:
133
            holder = provision['holder']
134
            source = provision['source']
135
            resource = provision['resource']
136
            quantity = provision['quantity']
137
            key = (holder, source, resource)
138
            lst.append((key, quantity))
139
            if not is_integer(quantity):
140
                raise ValueError()
141
        except (TypeError, KeyError, ValueError):
142
            raise BadRequest("Malformed provision %s" % str(provision))
143
    return lst
144

    
145

    
146
@csrf_exempt
147
@api.api_method(http_method='POST', token_required=True, user_required=False)
148
@component_from_token
149
def issue_commission(request):
150
    data = request.body
151
    try:
152
        input_data = json.loads(data)
153
    except json.JSONDecodeError:
154
        raise BadRequest("POST data should be in json format.")
155

    
156
    client_key = str(request.component_instance)
157
    provisions = input_data.get('provisions')
158
    if provisions is None:
159
        raise BadRequest("Provisions are missing.")
160
    if not isinstance(provisions, list):
161
        raise BadRequest("Provisions should be a list.")
162

    
163
    provisions = _provisions_to_list(provisions)
164
    force = input_data.get('force', False)
165
    if not isinstance(force, bool):
166
        raise BadRequest('"force" option should be a boolean.')
167

    
168
    auto_accept = input_data.get('auto_accept', False)
169
    if not isinstance(auto_accept, bool):
170
        raise BadRequest('"auto_accept" option should be a boolean.')
171

    
172
    name = input_data.get('name', "")
173
    if not isinstance(name, basestring):
174
        raise BadRequest("Commission name should be a string.")
175

    
176
    try:
177
        result = _issue_commission(clientkey=client_key,
178
                                   provisions=provisions,
179
                                   name=name,
180
                                   force=force,
181
                                   accept=auto_accept)
182
        data = {"serial": result}
183
        status_code = 201
184
    except (qh_exception.NoCapacityError,
185
            qh_exception.NoQuantityError) as e:
186
        status_code = 413
187
        body = {"message": e.message,
188
                "code": status_code,
189
                "data": e.data,
190
                }
191
        data = {"overLimit": body}
192
    except qh_exception.NoHoldingError as e:
193
        status_code = 404
194
        body = {"message": e.message,
195
                "code": status_code,
196
                "data": e.data,
197
                }
198
        data = {"itemNotFound": body}
199
    except qh_exception.InvalidDataError as e:
200
        status_code = 400
201
        body = {"message": e.message,
202
                "code": status_code,
203
                }
204
        data = {"badRequest": body}
205

    
206
    return json_response(data, status_code=status_code)
207

    
208

    
209
@transaction.commit_on_success
210
def _issue_commission(clientkey, provisions, name, force, accept):
211
    serial = qh.issue_commission(clientkey=clientkey,
212
                                 provisions=provisions,
213
                                 name=name,
214
                                 force=force)
215
    if accept:
216
        qh.resolve_pending_commission(clientkey=clientkey, serial=serial)
217

    
218
    return serial
219

    
220

    
221
def notFoundCF(serial):
222
    body = {"code": 404,
223
            "message": "serial %s does not exist" % serial,
224
            }
225
    return {"itemNotFound": body}
226

    
227

    
228
def conflictingCF(serial):
229
    body = {"code": 400,
230
            "message": "cannot both accept and reject serial %s" % serial,
231
            }
232
    return {"badRequest": body}
233

    
234

    
235
@csrf_exempt
236
@api.api_method(http_method='POST', token_required=True, user_required=False)
237
@component_from_token
238
@transaction.commit_on_success
239
def resolve_pending_commissions(request):
240
    data = request.body
241
    try:
242
        input_data = json.loads(data)
243
    except json.JSONDecodeError:
244
        raise BadRequest("POST data should be in json format.")
245

    
246
    client_key = str(request.component_instance)
247
    accept = input_data.get('accept', [])
248
    reject = input_data.get('reject', [])
249

    
250
    if not isinstance(accept, list) or not isinstance(reject, list):
251
        m = '"accept" and "reject" should reference lists of serials.'
252
        raise BadRequest(m)
253

    
254
    if not are_integer(accept) or not are_integer(reject):
255
        raise BadRequest("Serials should be integer.")
256

    
257
    result = qh.resolve_pending_commissions(clientkey=client_key,
258
                                            accept_set=accept,
259
                                            reject_set=reject)
260
    accepted, rejected, notFound, conflicting = result
261
    notFound = [(serial, notFoundCF(serial)) for serial in notFound]
262
    conflicting = [(serial, conflictingCF(serial)) for serial in conflicting]
263
    cloudfaults = notFound + conflicting
264
    data = {'accepted': accepted,
265
            'rejected': rejected,
266
            'failed': cloudfaults
267
            }
268

    
269
    return json_response(data)
270

    
271

    
272
@api.api_method(http_method='GET', token_required=True, user_required=False)
273
@component_from_token
274
def get_commission(request, serial):
275
    data = request.GET
276
    client_key = str(request.component_instance)
277
    try:
278
        serial = int(serial)
279
    except ValueError:
280
        raise BadRequest("Serial should be an integer.")
281

    
282
    try:
283
        data = qh.get_commission(clientkey=client_key,
284
                                 serial=serial)
285
        status_code = 200
286
        return json_response(data, status_code)
287
    except qh_exception.NoCommissionError:
288
        return HttpResponse(status=404)
289

    
290

    
291
@csrf_exempt
292
@api.api_method(http_method='POST', token_required=True, user_required=False)
293
@component_from_token
294
@transaction.commit_on_success
295
def serial_action(request, serial):
296
    data = request.body
297
    try:
298
        input_data = json.loads(data)
299
    except json.JSONDecodeError:
300
        raise BadRequest("POST data should be in json format.")
301

    
302
    try:
303
        serial = int(serial)
304
    except ValueError:
305
        raise BadRequest("Serial should be an integer.")
306

    
307
    client_key = str(request.component_instance)
308

    
309
    accept = 'accept' in input_data
310
    reject = 'reject' in input_data
311

    
312
    if accept == reject:
313
        raise BadRequest('Specify either accept or reject action.')
314

    
315
    result = qh.resolve_pending_commission(clientkey=client_key,
316
                                           serial=serial,
317
                                           accept=accept)
318
    response = HttpResponse()
319
    if not result:
320
        response.status_code = 404
321

    
322
    return response