Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / endpoints / qh.py @ ee45eb81

History | View | Annotate | Download (9.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
import logging
35
import itertools
36

    
37
from functools import wraps
38
from collections import namedtuple
39

    
40
from django.utils.translation import ugettext as _
41

    
42
from astakos.im.settings import (
43
        QUOTAHOLDER_URL, QUOTAHOLDER_TOKEN, LOGGING_LEVEL)
44

    
45
if QUOTAHOLDER_URL:
46
    from kamaki.clients.quotaholder import QuotaholderClient
47

    
48
ENTITY_KEY = '1'
49

    
50
inf = float('inf')
51

    
52
logger = logging.getLogger(__name__)
53

    
54
inf = float('inf')
55

    
56
clientkey = 'astakos'
57

    
58
_client = None
59
def get_client():
60
    global _client
61
    if _client:
62
        return _client
63
    if not QUOTAHOLDER_URL:
64
        return
65
    _client = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
66
    return _client
67

    
68
def call(func_name):
69
    """Decorator function for Quotaholder client calls."""
70
    def decorator(payload_func):
71
        @wraps(payload_func)
72
        def wrapper(entities=(), **kwargs):
73
            if not entities:
74
                return ()
75

    
76
            if not QUOTAHOLDER_URL:
77
                return ()
78

    
79
            c = get_client() 
80
            func = c.__dict__.get(func_name)
81
            if not func:
82
                return ()
83

    
84
            data = payload_func(entities, c, **kwargs)
85
            if not data:
86
                return data
87

    
88
            funcname = func.__name__
89
            kwargs = {'context': {}, funcname: data}
90
            rejected = func(**kwargs)
91
            msg = _('%s: %s - Rejected: %s' % (funcname, data, rejected,))
92
            logger.log(LOGGING_LEVEL, msg)
93
            return rejected
94
        return wrapper
95
    return decorator
96

    
97

    
98
@call('set_quota')
99
def send_quota(users, client=None):
100
    data = []
101
    append = data.append
102
    for user in users:
103
        for resource, uplimit in user.quota.iteritems():
104
            key = ENTITY_KEY
105
            quantity = None
106
            capacity = uplimit if uplimit != inf else None
107
            import_limit = None
108
            export_limit = None
109
            flags = 0
110
            args = (
111
                user.email, resource, key, quantity, capacity, import_limit,
112
                export_limit, flags)
113
            append(args)
114
    return data
115

    
116
QuotaLimits = namedtuple('QuotaLimits', ('holder',
117
                                         'capacity',
118
                                         'import_limit',
119
                                         'export_limit'))
120

    
121
def qh_add_quota(serial, quotalimits_list):
122
    if not QUOTAHOLDER_URL:
123
        return ()
124

    
125
    context = {}
126
    c = get_client()
127

    
128
    data = []
129
    append = data.append
130
    for ql in quotalimits_list:
131
        args = (ql.holder, ql.resource, ENTITY_KEY,
132
                0, ql.capacity, ql.import_limit, ql.export_limit)
133
        append(args)
134

    
135
    result = c.add_quota(context=context,
136
                         clientkey=clientkey,
137
                         serial=serial,
138
                         add_quota=data)
139

    
140
    return result
141

    
142
def qh_query_serials(serials):
143
    if not QUOTAHOLDER_URL:
144
        return ()
145

    
146
    context = {}
147
    c = get_client()
148
    result = c.query_serials(context=context,
149
                             clientkey=clientkey,
150
                             serials=serials)
151
    return result
152

    
153
def qh_ack_serials(serials):
154
    if not QUOTAHOLDER_URL:
155
        return ()
156

    
157
    context = {}
158
    c = get_client()
159
    result = c.ack_serials(context=context,
160
                           clientkey=clientkey,
161
                           serials=serials)
162
    return
163

    
164
@call('set_quota')
165
def send_resource_quantities(resources, client=None):
166
    data = []
167
    append = data.append
168
    for resource in resources:
169
        key = ENTITY_KEY
170
        quantity = resource.meta.filter(key='quantity') or None
171
        capacity = None
172
        import_limit = None
173
        export_limit = None
174
        flags = 0
175
        args = (resource.service, resource, key, quantity, capacity,
176
                import_limit, export_limit, flags)
177
        append(args)
178
    return data
179

    
180

    
181
@call('get_quota')
182
def get_quota(users, client=None):
183
    data = []
184
    append = data.append
185
    for user in users:
186
        try:
187
            entity = user.email
188
        except AttributeError:
189
            continue
190
        else:
191
            for r in user.quota.keys():
192
                args = entity, r, ENTITY_KEY
193
                append(args)
194
    return data
195

    
196

    
197
@call('create_entity')
198
def create_entities(entities, client=None, field=''):
199
    data = []
200
    append = data.append
201
    for entity in entities:
202
        try:
203
            entity = entity.__getattribute__(field)
204
        except AttributeError:
205
            continue
206
        owner = 'system'
207
        key = ENTITY_KEY
208
        ownerkey = ''
209
        args = entity, owner, key, ownerkey
210
        append(args)
211
    return data
212

    
213

    
214
def register_users(users, client=None):
215
    users, copy = itertools.tee(users)
216
    rejected = create_entities(entities=users,
217
                                       client=client,
218
                                       field='email')
219
    created = (e for e in copy if unicode(e) not in rejected)
220
    return send_quota(created)
221

    
222

    
223
def register_resources(resources, client=None):
224
    resources, copy = itertools.tee(resources)
225
    rejected = create_entities(entities=resources,
226
                                       client=client,
227
                                       field='service')
228
    created = (e for e in copy if unicode(e) not in rejected)
229
    return send_resource_quantities(created)
230

    
231

    
232
from datetime import datetime
233

    
234
strptime = datetime.strptime
235
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
236

    
237
SECOND_RESOLUTION = 1
238

    
239

    
240
def total_seconds(timedelta_object):
241
    return timedelta_object.seconds + timedelta_object.days * 86400
242

    
243

    
244
def iter_timeline(timeline, before):
245
    if not timeline:
246
        return
247

    
248
    for t in timeline:
249
        yield t
250

    
251
    t = dict(t)
252
    t['issue_time'] = before
253
    yield t
254

    
255

    
256
def _usage_units(timeline, after, before, details=0):
257

    
258
    t_total = 0
259
    uu_total = 0
260
    t_after = strptime(after, timefmt)
261
    t_before = strptime(before, timefmt)
262
    t0 = t_after
263
    u0 = 0
264

    
265
    for point in iter_timeline(timeline, before):
266
        issue_time = point['issue_time']
267

    
268
        if issue_time <= after:
269
            u0 = point['target_allocated_through']
270
            continue
271

    
272
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
273
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
274
        t_total += t_diff
275
        uu_cost = u0 * t_diff
276
        uu_total += uu_cost
277
        t0 = t
278
        u0 = point['target_allocated_through']
279

    
280
        target = point['target']
281
        if details:
282
            yield  (target,
283
                    point['resource'],
284
                    point['name'],
285
                    issue_time,
286
                    uu_cost,
287
                    uu_total)
288

    
289
    if not t_total:
290
        return
291

    
292
    yield  (target,
293
            'total',
294
            point['resource'],
295
            issue_time,
296
            uu_total / t_total,
297
            uu_total)
298

    
299

    
300
def usage_units(timeline, after, before, details=0):
301
    return list(_usage_units(timeline, after, before, details=details))
302

    
303

    
304
def traffic_units(timeline, after, before, details=0):
305
    tu_total = 0
306
    target = None
307
    issue_time = None
308

    
309
    for point in timeline:
310
        issue_time = point['issue_time']
311
        if issue_time <= after:
312
            continue
313
        if issue_time > before:
314
            break
315

    
316
        target = point['target']
317
        tu = point['target_allocated_through']
318
        tu_total += tu
319

    
320
        if details:
321
            yield  (target,
322
                    point['resource'],
323
                    point['name'],
324
                    issue_time,
325
                    tu,
326
                    tu_total)
327

    
328
    if not tu_total:
329
        return
330

    
331
    yield  (target,
332
            'total',
333
            point['resource'],
334
            issue_time,
335
            tu_total // len(timeline),
336
            tu_total)
337

    
338

    
339
def timeline_charge(entity, resource, after, before, details, charge_type):
340
    key = '1'
341
    if charge_type == 'charge_usage':
342
        charge_units = usage_units
343
    elif charge_type == 'charge_traffic':
344
        charge_units = traffic_units
345
    else:
346
        m = 'charge type %s not supported' % charge_type
347
        raise ValueError(m)
348

    
349
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
350
    timeline = quotaholder.get_timeline(
351
        context={},
352
        after=after,
353
        before=before,
354
        get_timeline=[[entity, resource, key]])
355
    cu = charge_units(timeline, after, before, details=details)
356
    return cu