Statistics
| Branch: | Tag: | Revision:

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

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, **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):
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.uuid, 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, sub_list, add_list):
122
    if not QUOTAHOLDER_URL:
123
        return ()
124

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

    
128
    sub_quota = []
129
    sub_append = sub_quota.append
130
    add_quota = []
131
    add_append = add_quota.append
132

    
133
    for ql in sub_quota:
134
        args = (ql.holder, ql.resource, ENTITY_KEY,
135
                0, ql.capacity, ql.import_limit, ql.export_limit)
136
        sub_append(args)
137

    
138
    for ql in add_quota:
139
        args = (ql.holder, ql.resource, ENTITY_KEY,
140
                0, ql.capacity, ql.import_limit, ql.export_limit)
141
        add_append(args)
142

    
143
    result = c.add_quota(context=context,
144
                         clientkey=clientkey,
145
                         serial=serial,
146
                         sub_quota=sub_quota,
147
                         add_quota=add_quota)
148

    
149
    return result
150

    
151
def qh_query_serials(serials):
152
    if not QUOTAHOLDER_URL:
153
        return ()
154

    
155
    context = {}
156
    c = get_client()
157
    result = c.query_serials(context=context,
158
                             clientkey=clientkey,
159
                             serials=serials)
160
    return result
161

    
162
def qh_ack_serials(serials):
163
    if not QUOTAHOLDER_URL:
164
        return ()
165

    
166
    context = {}
167
    c = get_client()
168
    result = c.ack_serials(context=context,
169
                           clientkey=clientkey,
170
                           serials=serials)
171
    return
172

    
173
@call('set_quota')
174
def send_resource_quantities(resources):
175
    data = []
176
    append = data.append
177
    for resource in resources:
178
        key = ENTITY_KEY
179
        quantity = resource.meta.filter(key='quantity') or None
180
        capacity = None
181
        import_limit = None
182
        export_limit = None
183
        flags = 0
184
        args = (resource.service, resource, key, quantity, capacity,
185
                import_limit, export_limit, flags)
186
        append(args)
187
    return data
188

    
189

    
190
@call('get_quota')
191
def get_quota(users):
192
    data = []
193
    append = data.append
194
    for user in users:
195
        try:
196
            entity = user.uuid
197
        except AttributeError:
198
            continue
199
        else:
200
            for r in user.quota.keys():
201
                args = entity, r, ENTITY_KEY
202
                append(args)
203
    return data
204

    
205

    
206
@call('create_entity')
207
def create_entities(entities, field=''):
208
    data = []
209
    append = data.append
210
    for entity in entities:
211
        try:
212
            entity = entity.__getattribute__(field)
213
        except AttributeError:
214
            continue
215
        owner = 'system'
216
        key = ENTITY_KEY
217
        ownerkey = ''
218
        args = entity, owner, key, ownerkey
219
        append(args)
220
    return data
221

    
222

    
223
def register_users(users):
224
    users, copy = itertools.tee(users)
225
    rejected = create_entities(entities=users, field='uuid')
226
    created = (e for e in copy if unicode(e) not in rejected)
227
    return send_quota(created)
228

    
229

    
230
def register_resources(resources):
231
    resources, copy = itertools.tee(resources)
232
    rejected = create_entities(entities=resources, field='service')
233
    created = (e for e in copy if unicode(e) not in rejected)
234
    return send_resource_quantities(created)
235

    
236

    
237
from datetime import datetime
238

    
239
strptime = datetime.strptime
240
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
241

    
242
SECOND_RESOLUTION = 1
243

    
244

    
245
def total_seconds(timedelta_object):
246
    return timedelta_object.seconds + timedelta_object.days * 86400
247

    
248

    
249
def iter_timeline(timeline, before):
250
    if not timeline:
251
        return
252

    
253
    for t in timeline:
254
        yield t
255

    
256
    t = dict(t)
257
    t['issue_time'] = before
258
    yield t
259

    
260

    
261
def _usage_units(timeline, after, before, details=0):
262

    
263
    t_total = 0
264
    uu_total = 0
265
    t_after = strptime(after, timefmt)
266
    t_before = strptime(before, timefmt)
267
    t0 = t_after
268
    u0 = 0
269

    
270
    for point in iter_timeline(timeline, before):
271
        issue_time = point['issue_time']
272

    
273
        if issue_time <= after:
274
            u0 = point['target_allocated_through']
275
            continue
276

    
277
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
278
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
279
        t_total += t_diff
280
        uu_cost = u0 * t_diff
281
        uu_total += uu_cost
282
        t0 = t
283
        u0 = point['target_allocated_through']
284

    
285
        target = point['target']
286
        if details:
287
            yield  (target,
288
                    point['resource'],
289
                    point['name'],
290
                    issue_time,
291
                    uu_cost,
292
                    uu_total)
293

    
294
    if not t_total:
295
        return
296

    
297
    yield  (target,
298
            'total',
299
            point['resource'],
300
            issue_time,
301
            uu_total / t_total,
302
            uu_total)
303

    
304

    
305
def usage_units(timeline, after, before, details=0):
306
    return list(_usage_units(timeline, after, before, details=details))
307

    
308

    
309
def traffic_units(timeline, after, before, details=0):
310
    tu_total = 0
311
    target = None
312
    issue_time = None
313

    
314
    for point in timeline:
315
        issue_time = point['issue_time']
316
        if issue_time <= after:
317
            continue
318
        if issue_time > before:
319
            break
320

    
321
        target = point['target']
322
        tu = point['target_allocated_through']
323
        tu_total += tu
324

    
325
        if details:
326
            yield  (target,
327
                    point['resource'],
328
                    point['name'],
329
                    issue_time,
330
                    tu,
331
                    tu_total)
332

    
333
    if not tu_total:
334
        return
335

    
336
    yield  (target,
337
            'total',
338
            point['resource'],
339
            issue_time,
340
            tu_total // len(timeline),
341
            tu_total)
342

    
343

    
344
def timeline_charge(entity, resource, after, before, details, charge_type):
345
    key = '1'
346
    if charge_type == 'charge_usage':
347
        charge_units = usage_units
348
    elif charge_type == 'charge_traffic':
349
        charge_units = traffic_units
350
    else:
351
        m = 'charge type %s not supported' % charge_type
352
        raise ValueError(m)
353

    
354
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
355
    timeline = quotaholder.get_timeline(
356
        context={},
357
        after=after,
358
        before=before,
359
        get_timeline=[[entity, resource, key]])
360
    cu = charge_units(timeline, after, before, details=details)
361
    return cu