Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (13.8 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
    from kamaki.clients.quotaholder import QH_PRACTICALLY_INFINITE
48

    
49
from synnefo.util.number import strbigdec
50

    
51
ENTITY_KEY = '1'
52

    
53

    
54
logger = logging.getLogger(__name__)
55

    
56
inf = float('inf')
57

    
58
clientkey = 'astakos'
59

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

    
70
def set_quota(payload):
71
    c = get_client()
72
    if not c:
73
        return
74
    result = c.set_quota(context={}, clientkey=clientkey, set_quota=payload)
75
    logger.debug('set_quota: %s rejected: %s' % (payload, result))
76
    return result
77

    
78
def get_entity(payload):
79
    c = get_client()
80
    if not c:
81
        return
82
    result = c.get_entity(context={}, get_entity=payload)
83
    logger.debug('get_entity: %s reply: %s' % (payload, result))
84
    return result
85

    
86
def get_holding(payload):
87
    c = get_client()
88
    if not c:
89
        return
90
    result = c.get_holding(context={}, get_holding=payload)
91
    logger.debug('get_holding: %s reply: %s' % (payload, result))
92
    return result
93

    
94
def qh_get_holdings(users, resources):
95
    payload = []
96
    append = payload.append
97
    for user in users:
98
        for resource in resources:
99
            append((user.uuid, resource, ENTITY_KEY),)
100
    result = get_holding(payload)
101
    return result
102

    
103
def quota_limits_per_user_from_get(lst):
104
    quotas = {}
105
    for holder, resource, q, c, il, el, imp, exp, ret, rel, flags in lst:
106
        userquotas = quotas.get(holder, {})
107
        userquotas[resource] = QuotaValues(quantity=q, capacity=c,
108
                                           import_limit=il, export_limit=el)
109
        quotas[holder] = userquotas
110
    return quotas
111

    
112
def qh_get_quota(users, resources):
113
    c = get_client()
114
    if not c:
115
        return
116
    payload = []
117
    append = payload.append
118
    for user in users:
119
        for resource in resources:
120
            append((user.uuid, resource, ENTITY_KEY),)
121

    
122
    result = c.get_quota(context={}, clientkey=clientkey, get_quota=payload)
123
    logger.debug('get_quota: %s rejected: %s' % (payload, result))
124
    return result
125

    
126
def qh_get_quota_limits(users, resources):
127
    result = qh_get_quota(users, resources)
128
    return quota_limits_per_user_from_get(result)
129

    
130
def create_entity(payload):
131
    c = get_client()
132
    if not c:
133
        return
134
    result = c.create_entity(
135
        context={}, clientkey=clientkey, create_entity=payload)
136
    logger.debug('create_entity: %s rejected: %s' % (payload, result))
137
    return result
138

    
139
SetQuotaPayload = namedtuple('SetQuotaPayload', ('holder',
140
                                                 'resource',
141
                                                 'key',
142
                                                 'quantity',
143
                                                 'capacity',
144
                                                 'import_limit',
145
                                                 'export_limit',
146
                                                 'flags'))
147

    
148
GetQuotaPayload = namedtuple('GetQuotaPayload', ('holder',
149
                                                 'resource',
150
                                                 'key'))
151

    
152
CreateEntityPayload = namedtuple('CreateEntityPayload', ('entity',
153
                                                         'owner',
154
                                                         'key',
155
                                                         'ownerkey'))
156
QuotaLimits = namedtuple('QuotaLimits', ('holder',
157
                                         'resource',
158
                                         'capacity',
159
                                        'import_limit',
160
                                         'export_limit'))
161

    
162
class QuotaValues(namedtuple('QuotaValues', ('quantity',
163
                                             'capacity',
164
                                             'import_limit',
165
                                             'export_limit'))):
166
    __slots__ = ()
167
    def __dir__(self):
168
            return ['quantity', 'capacity', 'import_limit', 'export_limit']
169

    
170
    def __str__(self):
171
        return '\t'.join(['%s=%s' % (f, strbigdec(getattr(self, f))) for f in dir(self)])
172

    
173
def add_quota_values(q1, q2):
174
    return QuotaValues(
175
        quantity = q1.quantity + q2.quantity,
176
        capacity = q1.capacity + q2.capacity,
177
        import_limit = q1.import_limit + q2.import_limit,
178
        export_limit = q1.export_limit + q2.export_limit)
179

    
180
def qh_register_user_with_quotas(user):
181
    return register_users_with_quotas([user])
182

    
183
def register_users_with_quotas(users):
184
    rejected = register_users(users)
185
    if not rejected:
186
        register_quotas(users)
187

    
188
def register_users(users):
189
    if not users:
190
        return
191

    
192
    payload = list(CreateEntityPayload(
193
                    entity=u.uuid,
194
                    owner='system',
195
                    key=ENTITY_KEY,
196
                    ownerkey='') for u in users)
197
    return create_entity(payload)
198

    
199
def register_quotas(users):
200
    if not users:
201
        return
202

    
203
    payload = []
204
    append = payload.append
205
    for u in users:
206
        for resource, q in u.all_quotas().iteritems():
207
            append( SetQuotaPayload(
208
                    holder=u.uuid,
209
                    resource=resource,
210
                    key=ENTITY_KEY,
211
                    quantity=q.quantity,
212
                    capacity=q.capacity,
213
                    import_limit=q.import_limit,
214
                    export_limit=q.export_limit,
215
                    flags=0))
216
    return set_quota(payload)
217

    
218
def send_quotas(userquotas):
219
    if not userquotas:
220
        return
221

    
222
    payload = []
223
    append = payload.append
224
    for holder, quotas in userquotas.iteritems():
225
        for resource, q in quotas.iteritems():
226
            append( SetQuotaPayload(
227
                    holder=holder,
228
                    resource=resource,
229
                    key=ENTITY_KEY,
230
                    quantity=q.quantity,
231
                    capacity=q.capacity,
232
                    import_limit=q.import_limit,
233
                    export_limit=q.export_limit,
234
                    flags=0))
235
    return set_quota(payload)
236

    
237
def register_services(services):
238
    def payload(services):
239
        return list(CreateEntityPayload(
240
                entity=service,
241
                owner='system',
242
                key=ENTITY_KEY,
243
                ownerkey='')
244
                    for service in set(services))
245

    
246
    if not services:
247
        return
248
    existing = create_entity(payload(services))
249
    if 0 < len(existing) < len(services):
250
        nonexisting = [s for i, s in enumerate(services)
251
                       if i not in existing]
252
        r = create_entity(payload(nonexisting))
253
        if r:
254
            failed = [s for i, s in enumerate(nonexisting)
255
                      if i in r]
256
            m = "Failed to register services: %s" % (failed,)
257
            raise RuntimeError(m)
258

    
259
def register_resources(resources):
260
    try:
261
        QH_PRACTICALLY_INFINITE
262
    except NameError:
263
        return
264

    
265
    payload = list(SetQuotaPayload(
266
            holder=resource.service,
267
            resource=resource,
268
            key=ENTITY_KEY,
269
            quantity=QH_PRACTICALLY_INFINITE,
270
            capacity=QH_PRACTICALLY_INFINITE,
271
            import_limit=QH_PRACTICALLY_INFINITE,
272
            export_limit=QH_PRACTICALLY_INFINITE,
273
            flags=0) for resource in resources)
274
    return set_quota(payload)
275

    
276
def qh_check_users(users):
277
    payload = [(u.uuid, ENTITY_KEY) for u in users]
278
    result = get_entity(payload)
279
    uuids = [entity for (entity, owner) in result]
280

    
281
    existing = []
282
    nonexisting = []
283
    for u in users:
284
        if u.uuid in uuids:
285
            existing.append(u)
286
        else:
287
            nonexisting.append(u)
288
    return (existing, nonexisting)
289

    
290
def qh_add_quota(serial, sub_list, add_list):
291
    if not QUOTAHOLDER_URL:
292
        return ()
293

    
294
    context = {}
295
    c = get_client()
296

    
297
    sub_quota = []
298
    sub_append = sub_quota.append
299
    add_quota = []
300
    add_append = add_quota.append
301

    
302
    for ql in sub_list:
303
        args = (ql.holder, ql.resource, ENTITY_KEY,
304
                0, ql.capacity, ql.import_limit, ql.export_limit)
305
        sub_append(args)
306

    
307
    for ql in add_list:
308
        args = (ql.holder, ql.resource, ENTITY_KEY,
309
                0, ql.capacity, ql.import_limit, ql.export_limit)
310
        add_append(args)
311

    
312
    result = c.add_quota(context=context,
313
                         clientkey=clientkey,
314
                         serial=serial,
315
                         sub_quota=sub_quota,
316
                         add_quota=add_quota)
317

    
318
    return result
319

    
320
def qh_query_serials(serials):
321
    if not QUOTAHOLDER_URL:
322
        return ()
323

    
324
    context = {}
325
    c = get_client()
326
    result = c.query_serials(context=context,
327
                             clientkey=clientkey,
328
                             serials=serials)
329
    return result
330

    
331
def qh_ack_serials(serials):
332
    if not QUOTAHOLDER_URL:
333
        return ()
334

    
335
    context = {}
336
    c = get_client()
337
    result = c.ack_serials(context=context,
338
                           clientkey=clientkey,
339
                           serials=serials)
340
    return
341

    
342
from datetime import datetime
343

    
344
strptime = datetime.strptime
345
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
346

    
347
SECOND_RESOLUTION = 1
348

    
349

    
350
def total_seconds(timedelta_object):
351
    return timedelta_object.seconds + timedelta_object.days * 86400
352

    
353

    
354
def iter_timeline(timeline, before):
355
    if not timeline:
356
        return
357

    
358
    for t in timeline:
359
        yield t
360

    
361
    t = dict(t)
362
    t['issue_time'] = before
363
    yield t
364

    
365

    
366
def _usage_units(timeline, after, before, details=0):
367

    
368
    t_total = 0
369
    uu_total = 0
370
    t_after = strptime(after, timefmt)
371
    t_before = strptime(before, timefmt)
372
    t0 = t_after
373
    u0 = 0
374

    
375
    for point in iter_timeline(timeline, before):
376
        issue_time = point['issue_time']
377

    
378
        if issue_time <= after:
379
            u0 = point['target_allocated_through']
380
            continue
381

    
382
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
383
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
384
        t_total += t_diff
385
        uu_cost = u0 * t_diff
386
        uu_total += uu_cost
387
        t0 = t
388
        u0 = point['target_allocated_through']
389

    
390
        target = point['target']
391
        if details:
392
            yield  (target,
393
                    point['resource'],
394
                    point['name'],
395
                    issue_time,
396
                    uu_cost,
397
                    uu_total)
398

    
399
    if not t_total:
400
        return
401

    
402
    yield  (target,
403
            'total',
404
            point['resource'],
405
            issue_time,
406
            uu_total / t_total,
407
            uu_total)
408

    
409

    
410
def usage_units(timeline, after, before, details=0):
411
    return list(_usage_units(timeline, after, before, details=details))
412

    
413

    
414
def traffic_units(timeline, after, before, details=0):
415
    tu_total = 0
416
    target = None
417
    issue_time = None
418

    
419
    for point in timeline:
420
        issue_time = point['issue_time']
421
        if issue_time <= after:
422
            continue
423
        if issue_time > before:
424
            break
425

    
426
        target = point['target']
427
        tu = point['target_allocated_through']
428
        tu_total += tu
429

    
430
        if details:
431
            yield  (target,
432
                    point['resource'],
433
                    point['name'],
434
                    issue_time,
435
                    tu,
436
                    tu_total)
437

    
438
    if not tu_total:
439
        return
440

    
441
    yield  (target,
442
            'total',
443
            point['resource'],
444
            issue_time,
445
            tu_total // len(timeline),
446
            tu_total)
447

    
448

    
449
def timeline_charge(entity, resource, after, before, details, charge_type):
450
    key = '1'
451
    if charge_type == 'charge_usage':
452
        charge_units = usage_units
453
    elif charge_type == 'charge_traffic':
454
        charge_units = traffic_units
455
    else:
456
        m = 'charge type %s not supported' % charge_type
457
        raise ValueError(m)
458

    
459
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
460
    timeline = quotaholder.get_timeline(
461
        context={},
462
        after=after,
463
        before=before,
464
        get_timeline=[[entity, resource, key]])
465
    cu = charge_units(timeline, after, before, details=details)
466
    return cu