Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.7 kB)

1
# Copyright 2011, 2012, 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
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

    
62

    
63
def get_client():
64
    global _client
65
    if _client:
66
        return _client
67
    if not QUOTAHOLDER_URL:
68
        return
69
    _client = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
70
    return _client
71

    
72

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

    
81

    
82
def get_entity(payload):
83
    c = get_client()
84
    if not c:
85
        return
86
    result = c.get_entity(context={}, get_entity=payload)
87
    logger.debug('get_entity: %s reply: %s' % (payload, result))
88
    return result
89

    
90

    
91
def get_holding(payload):
92
    c = get_client()
93
    if not c:
94
        return
95
    result = c.get_holding(context={}, get_holding=payload)
96
    logger.debug('get_holding: %s reply: %s' % (payload, result))
97
    return result
98

    
99

    
100
def qh_get_holdings(users, resources):
101
    payload = []
102
    append = payload.append
103
    for user in users:
104
        for resource in resources:
105
            append((user.uuid, resource, ENTITY_KEY),)
106
    result = get_holding(payload)
107
    return result
108

    
109

    
110
def quota_limits_per_user_from_get(lst):
111
    quotas = {}
112
    for holder, resource, q, c, il, el, imp, exp, ret, rel, flags in lst:
113
        userquotas = quotas.get(holder, {})
114
        userquotas[resource] = QuotaValues(quantity=q, capacity=c,
115
                                           import_limit=il, export_limit=el)
116
        quotas[holder] = userquotas
117
    return quotas
118

    
119

    
120
def quotas_per_user_from_get(lst):
121
    limits = {}
122
    counters = {}
123
    for holder, resource, q, c, il, el, imp, exp, ret, rel, flags in lst:
124
        userlimits = limits.get(holder, {})
125
        userlimits[resource] = QuotaValues(quantity=q, capacity=c,
126
                                           import_limit=il, export_limit=el)
127
        limits[holder] = userlimits
128

    
129
        usercounters = counters.get(holder, {})
130
        usercounters[resource] = QuotaCounters(imported=imp, exported=exp,
131
                                               returned=ret, released=rel)
132
        counters[holder] = usercounters
133
    return limits, counters
134

    
135

    
136
def qh_get_quota(users, resources):
137
    c = get_client()
138
    if not c:
139
        return
140
    payload = []
141
    append = payload.append
142
    for user in users:
143
        for resource in resources:
144
            append((user.uuid, resource, ENTITY_KEY),)
145

    
146
    result = c.get_quota(context={}, clientkey=clientkey, get_quota=payload)
147
    logger.debug('get_quota: %s rejected: %s' % (payload, result))
148
    return result
149

    
150

    
151
def qh_get_quota_limits(users, resources):
152
    result = qh_get_quota(users, resources)
153
    return quota_limits_per_user_from_get(result)
154

    
155

    
156
def qh_get_quotas(users, resources):
157
    result = qh_get_quota(users, resources)
158
    return quotas_per_user_from_get(result)
159

    
160

    
161
def create_entity(payload):
162
    c = get_client()
163
    if not c:
164
        return
165
    result = c.create_entity(
166
        context={}, clientkey=clientkey, create_entity=payload)
167
    logger.debug('create_entity: %s rejected: %s' % (payload, result))
168
    return result
169

    
170
SetQuotaPayload = namedtuple('SetQuotaPayload', ('holder',
171
                                                 'resource',
172
                                                 'key',
173
                                                 'quantity',
174
                                                 'capacity',
175
                                                 'import_limit',
176
                                                 'export_limit',
177
                                                 'flags'))
178

    
179
GetQuotaPayload = namedtuple('GetQuotaPayload', ('holder',
180
                                                 'resource',
181
                                                 'key'))
182

    
183
CreateEntityPayload = namedtuple('CreateEntityPayload', ('entity',
184
                                                         'owner',
185
                                                         'key',
186
                                                         'ownerkey'))
187
QuotaLimits = namedtuple('QuotaLimits', ('holder',
188
                                         'resource',
189
                                         'capacity',
190
                                         'import_limit',
191
                                         'export_limit'))
192

    
193
QuotaCounters = namedtuple('QuotaCounters', ('imported',
194
                                             'exported',
195
                                             'returned',
196
                                             'released'))
197

    
198

    
199
class QuotaValues(namedtuple('QuotaValues', ('quantity',
200
                                             'capacity',
201
                                             'import_limit',
202
                                             'export_limit'))):
203
    __slots__ = ()
204

    
205
    def __dir__(self):
206
            return ['quantity', 'capacity', 'import_limit', 'export_limit']
207

    
208
    def __str__(self):
209
        return '\t'.join(['%s=%s' % (f, strbigdec(getattr(self, f)))
210
                          for f in dir(self)])
211

    
212

    
213
def add_quota_values(q1, q2):
214
    return QuotaValues(
215
        quantity = q1.quantity + q2.quantity,
216
        capacity = q1.capacity + q2.capacity,
217
        import_limit = q1.import_limit + q2.import_limit,
218
        export_limit = q1.export_limit + q2.export_limit)
219

    
220

    
221
def qh_register_user_with_quotas(user):
222
    return register_users_with_quotas([user])
223

    
224

    
225
def register_users_with_quotas(users):
226
    rejected = register_users(users)
227
    if not rejected:
228
        register_quotas(users)
229

    
230

    
231
def register_users(users):
232
    if not users:
233
        return
234

    
235
    payload = list(CreateEntityPayload(
236
            entity=u.uuid,
237
            owner='system',
238
            key=ENTITY_KEY,
239
            ownerkey='') for u in users)
240
    return create_entity(payload)
241

    
242

    
243
def register_quotas(users):
244
    if not users:
245
        return
246

    
247
    payload = []
248
    append = payload.append
249
    for u in users:
250
        for resource, q in u.all_quotas().iteritems():
251
            append(SetQuotaPayload(
252
                    holder=u.uuid,
253
                    resource=resource,
254
                    key=ENTITY_KEY,
255
                    quantity=q.quantity,
256
                    capacity=q.capacity,
257
                    import_limit=q.import_limit,
258
                    export_limit=q.export_limit,
259
                    flags=0))
260
    return set_quota(payload)
261

    
262

    
263
def send_quotas(userquotas):
264
    if not userquotas:
265
        return
266

    
267
    payload = []
268
    append = payload.append
269
    for holder, quotas in userquotas.iteritems():
270
        for resource, q in quotas.iteritems():
271
            append(SetQuotaPayload(
272
                    holder=holder,
273
                    resource=resource,
274
                    key=ENTITY_KEY,
275
                    quantity=q.quantity,
276
                    capacity=q.capacity,
277
                    import_limit=q.import_limit,
278
                    export_limit=q.export_limit,
279
                    flags=0))
280
    return set_quota(payload)
281

    
282

    
283
def register_services(services):
284
    def payload(services):
285
        return list(CreateEntityPayload(
286
                entity=service,
287
                owner='system',
288
                key=ENTITY_KEY,
289
                ownerkey='')
290
                    for service in set(services))
291

    
292
    if not services:
293
        return
294
    existing = create_entity(payload(services))
295
    if 0 < len(existing) < len(services):
296
        nonexisting = [s for i, s in enumerate(services)
297
                       if i not in existing]
298
        r = create_entity(payload(nonexisting))
299
        if r:
300
            failed = [s for i, s in enumerate(nonexisting)
301
                      if i in r]
302
            m = "Failed to register services: %s" % (failed,)
303
            raise RuntimeError(m)
304

    
305

    
306
def register_resources(resources):
307
    try:
308
        QH_PRACTICALLY_INFINITE
309
    except NameError:
310
        return
311

    
312
    payload = list(SetQuotaPayload(
313
            holder=resource.service,
314
            resource=resource,
315
            key=ENTITY_KEY,
316
            quantity=QH_PRACTICALLY_INFINITE,
317
            capacity=QH_PRACTICALLY_INFINITE,
318
            import_limit=QH_PRACTICALLY_INFINITE,
319
            export_limit=QH_PRACTICALLY_INFINITE,
320
            flags=0) for resource in resources)
321
    return set_quota(payload)
322

    
323

    
324
def qh_check_users(users):
325
    payload = [(u.uuid, ENTITY_KEY) for u in users]
326
    result = get_entity(payload)
327
    uuids = [entity for (entity, owner) in result]
328

    
329
    existing = []
330
    nonexisting = []
331
    for u in users:
332
        if u.uuid in uuids:
333
            existing.append(u)
334
        else:
335
            nonexisting.append(u)
336
    return (existing, nonexisting)
337

    
338

    
339
def qh_add_quota(serial, sub_list, add_list):
340
    if not QUOTAHOLDER_URL:
341
        return ()
342

    
343
    context = {}
344
    c = get_client()
345

    
346
    sub_quota = []
347
    sub_append = sub_quota.append
348
    add_quota = []
349
    add_append = add_quota.append
350

    
351
    for ql in sub_list:
352
        args = (ql.holder, ql.resource, ENTITY_KEY,
353
                0, ql.capacity, ql.import_limit, ql.export_limit)
354
        sub_append(args)
355

    
356
    for ql in add_list:
357
        args = (ql.holder, ql.resource, ENTITY_KEY,
358
                0, ql.capacity, ql.import_limit, ql.export_limit)
359
        add_append(args)
360

    
361
    result = c.add_quota(context=context,
362
                         clientkey=clientkey,
363
                         serial=serial,
364
                         sub_quota=sub_quota,
365
                         add_quota=add_quota)
366

    
367
    return result
368

    
369

    
370
def qh_query_serials(serials):
371
    if not QUOTAHOLDER_URL:
372
        return ()
373

    
374
    context = {}
375
    c = get_client()
376
    result = c.query_serials(context=context,
377
                             clientkey=clientkey,
378
                             serials=serials)
379
    return result
380

    
381

    
382
def qh_ack_serials(serials):
383
    if not QUOTAHOLDER_URL:
384
        return ()
385

    
386
    context = {}
387
    c = get_client()
388
    result = c.ack_serials(context=context,
389
                           clientkey=clientkey,
390
                           serials=serials)
391
    return
392

    
393
from datetime import datetime
394

    
395
strptime = datetime.strptime
396
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
397

    
398
SECOND_RESOLUTION = 1
399

    
400

    
401
def total_seconds(timedelta_object):
402
    return timedelta_object.seconds + timedelta_object.days * 86400
403

    
404

    
405
def iter_timeline(timeline, before):
406
    if not timeline:
407
        return
408

    
409
    for t in timeline:
410
        yield t
411

    
412
    t = dict(t)
413
    t['issue_time'] = before
414
    yield t
415

    
416

    
417
def _usage_units(timeline, after, before, details=0):
418

    
419
    t_total = 0
420
    uu_total = 0
421
    t_after = strptime(after, timefmt)
422
    t_before = strptime(before, timefmt)
423
    t0 = t_after
424
    u0 = 0
425

    
426
    for point in iter_timeline(timeline, before):
427
        issue_time = point['issue_time']
428

    
429
        if issue_time <= after:
430
            u0 = point['target_allocated_through']
431
            continue
432

    
433
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
434
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
435
        t_total += t_diff
436
        uu_cost = u0 * t_diff
437
        uu_total += uu_cost
438
        t0 = t
439
        u0 = point['target_allocated_through']
440

    
441
        target = point['target']
442
        if details:
443
            yield (target,
444
                   point['resource'],
445
                   point['name'],
446
                   issue_time,
447
                   uu_cost,
448
                   uu_total)
449

    
450
    if not t_total:
451
        return
452

    
453
    yield (target,
454
           'total',
455
           point['resource'],
456
           issue_time,
457
           uu_total / t_total,
458
           uu_total)
459

    
460

    
461
def usage_units(timeline, after, before, details=0):
462
    return list(_usage_units(timeline, after, before, details=details))
463

    
464

    
465
def traffic_units(timeline, after, before, details=0):
466
    tu_total = 0
467
    target = None
468
    issue_time = None
469

    
470
    for point in timeline:
471
        issue_time = point['issue_time']
472
        if issue_time <= after:
473
            continue
474
        if issue_time > before:
475
            break
476

    
477
        target = point['target']
478
        tu = point['target_allocated_through']
479
        tu_total += tu
480

    
481
        if details:
482
            yield (target,
483
                   point['resource'],
484
                   point['name'],
485
                   issue_time,
486
                   tu,
487
                   tu_total)
488

    
489
    if not tu_total:
490
        return
491

    
492
    yield (target,
493
           'total',
494
           point['resource'],
495
           issue_time,
496
           tu_total // len(timeline),
497
           tu_total)
498

    
499

    
500
def timeline_charge(entity, resource, after, before, details, charge_type):
501
    key = '1'
502
    if charge_type == 'charge_usage':
503
        charge_units = usage_units
504
    elif charge_type == 'charge_traffic':
505
        charge_units = traffic_units
506
    else:
507
        m = 'charge type %s not supported' % charge_type
508
        raise ValueError(m)
509

    
510
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
511
    timeline = quotaholder.get_timeline(
512
        context={},
513
        after=after,
514
        before=before,
515
        get_timeline=[[entity, resource, key]])
516
    cu = charge_units(timeline, after, before, details=details)
517
    return cu