Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (14.9 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
    if payload == []:
78
        return []
79
    result = c.set_quota(context={}, clientkey=clientkey, set_quota=payload)
80
    logger.debug('set_quota: %s rejected: %s' % (payload, result))
81
    return result
82

    
83

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

    
94

    
95
def get_holding(payload):
96
    c = get_client()
97
    if not c:
98
        return
99
    if payload == []:
100
        return []
101
    result = c.get_holding(context={}, get_holding=payload)
102
    logger.debug('get_holding: %s reply: %s' % (payload, result))
103
    return result
104

    
105

    
106
def qh_get_holdings(users, resources):
107
    payload = []
108
    append = payload.append
109
    for user in users:
110
        for resource in resources:
111
            append((user.uuid, resource, ENTITY_KEY),)
112
    result = get_holding(payload)
113
    return result
114

    
115

    
116
def quota_limits_per_user_from_get(lst):
117
    quotas = {}
118
    for holder, resource, q, c, il, el, imp, exp, ret, rel, flags in lst:
119
        userquotas = quotas.get(holder, {})
120
        userquotas[resource] = QuotaValues(quantity=q, capacity=c,
121
                                           import_limit=il, export_limit=el)
122
        quotas[holder] = userquotas
123
    return quotas
124

    
125

    
126
def quotas_per_user_from_get(lst):
127
    limits = {}
128
    counters = {}
129
    for holder, resource, q, c, il, el, imp, exp, ret, rel, flags in lst:
130
        userlimits = limits.get(holder, {})
131
        userlimits[resource] = QuotaValues(quantity=q, capacity=c,
132
                                           import_limit=il, export_limit=el)
133
        limits[holder] = userlimits
134

    
135
        usercounters = counters.get(holder, {})
136
        usercounters[resource] = QuotaCounters(imported=imp, exported=exp,
137
                                               returned=ret, released=rel)
138
        counters[holder] = usercounters
139
    return limits, counters
140

    
141

    
142
def qh_get_quota(users, resources):
143
    c = get_client()
144
    if not c:
145
        return
146
    payload = []
147
    append = payload.append
148
    for user in users:
149
        for resource in resources:
150
            append((user.uuid, resource, ENTITY_KEY),)
151

    
152
    if payload == []:
153
        return []
154
    result = c.get_quota(context={}, clientkey=clientkey, get_quota=payload)
155
    logger.debug('get_quota: %s rejected: %s' % (payload, result))
156
    return result
157

    
158

    
159
def qh_get_quota_limits(users, resources):
160
    result = qh_get_quota(users, resources)
161
    return quota_limits_per_user_from_get(result)
162

    
163

    
164
def qh_get_quotas(users, resources):
165
    result = qh_get_quota(users, resources)
166
    return quotas_per_user_from_get(result)
167

    
168

    
169
def create_entity(payload):
170
    c = get_client()
171
    if not c:
172
        return
173
    if payload == []:
174
        return []
175
    result = c.create_entity(
176
        context={}, clientkey=clientkey, create_entity=payload)
177
    logger.debug('create_entity: %s rejected: %s' % (payload, result))
178
    return result
179

    
180
SetQuotaPayload = namedtuple('SetQuotaPayload', ('holder',
181
                                                 'resource',
182
                                                 'key',
183
                                                 'quantity',
184
                                                 'capacity',
185
                                                 'import_limit',
186
                                                 'export_limit',
187
                                                 'flags'))
188

    
189
GetQuotaPayload = namedtuple('GetQuotaPayload', ('holder',
190
                                                 'resource',
191
                                                 'key'))
192

    
193
CreateEntityPayload = namedtuple('CreateEntityPayload', ('entity',
194
                                                         'owner',
195
                                                         'key',
196
                                                         'ownerkey'))
197
QuotaLimits = namedtuple('QuotaLimits', ('holder',
198
                                         'resource',
199
                                         'capacity',
200
                                         'import_limit',
201
                                         'export_limit'))
202

    
203
QuotaCounters = namedtuple('QuotaCounters', ('imported',
204
                                             'exported',
205
                                             'returned',
206
                                             'released'))
207

    
208

    
209
class QuotaValues(namedtuple('QuotaValues', ('quantity',
210
                                             'capacity',
211
                                             'import_limit',
212
                                             'export_limit'))):
213
    __slots__ = ()
214

    
215
    def __dir__(self):
216
            return ['quantity', 'capacity', 'import_limit', 'export_limit']
217

    
218
    def __str__(self):
219
        return '\t'.join(['%s=%s' % (f, strbigdec(getattr(self, f)))
220
                          for f in dir(self)])
221

    
222

    
223
def add_quota_values(q1, q2):
224
    return QuotaValues(
225
        quantity = q1.quantity + q2.quantity,
226
        capacity = q1.capacity + q2.capacity,
227
        import_limit = q1.import_limit + q2.import_limit,
228
        export_limit = q1.export_limit + q2.export_limit)
229

    
230

    
231
def qh_register_user_with_quotas(user):
232
    return register_users_with_quotas([user])
233

    
234

    
235
def register_users_with_quotas(users):
236
    rejected = register_users(users)
237
    if not rejected:
238
        register_quotas(users)
239

    
240

    
241
def register_users(users):
242
    if not users:
243
        return
244

    
245
    payload = list(CreateEntityPayload(
246
            entity=u.uuid,
247
            owner='system',
248
            key=ENTITY_KEY,
249
            ownerkey='') for u in users)
250
    return create_entity(payload)
251

    
252

    
253
def register_quotas(users):
254
    if not users:
255
        return
256

    
257
    payload = []
258
    append = payload.append
259
    for u in users:
260
        for resource, q in u.all_quotas().iteritems():
261
            append(SetQuotaPayload(
262
                    holder=u.uuid,
263
                    resource=resource,
264
                    key=ENTITY_KEY,
265
                    quantity=q.quantity,
266
                    capacity=q.capacity,
267
                    import_limit=q.import_limit,
268
                    export_limit=q.export_limit,
269
                    flags=0))
270
    return set_quota(payload)
271

    
272

    
273
def send_quotas(userquotas):
274
    if not userquotas:
275
        return
276

    
277
    payload = []
278
    append = payload.append
279
    for holder, quotas in userquotas.iteritems():
280
        for resource, q in quotas.iteritems():
281
            append(SetQuotaPayload(
282
                    holder=holder,
283
                    resource=resource,
284
                    key=ENTITY_KEY,
285
                    quantity=q.quantity,
286
                    capacity=q.capacity,
287
                    import_limit=q.import_limit,
288
                    export_limit=q.export_limit,
289
                    flags=0))
290
    return set_quota(payload)
291

    
292

    
293
def register_services(services):
294
    def payload(services):
295
        return list(CreateEntityPayload(
296
                entity=service,
297
                owner='system',
298
                key=ENTITY_KEY,
299
                ownerkey='')
300
                    for service in set(services))
301

    
302
    if not services:
303
        return
304
    existing = create_entity(payload(services))
305
    if 0 < len(existing) < len(services):
306
        nonexisting = [s for i, s in enumerate(services)
307
                       if i not in existing]
308
        r = create_entity(payload(nonexisting))
309
        if r:
310
            failed = [s for i, s in enumerate(nonexisting)
311
                      if i in r]
312
            m = "Failed to register services: %s" % (failed,)
313
            raise RuntimeError(m)
314

    
315

    
316
def register_resources(resources):
317
    try:
318
        QH_PRACTICALLY_INFINITE
319
    except NameError:
320
        return
321

    
322
    payload = list(SetQuotaPayload(
323
            holder=resource.service,
324
            resource=resource,
325
            key=ENTITY_KEY,
326
            quantity=QH_PRACTICALLY_INFINITE,
327
            capacity=QH_PRACTICALLY_INFINITE,
328
            import_limit=QH_PRACTICALLY_INFINITE,
329
            export_limit=QH_PRACTICALLY_INFINITE,
330
            flags=0) for resource in resources)
331
    return set_quota(payload)
332

    
333

    
334
def qh_check_users(users):
335
    payload = [(u.uuid, ENTITY_KEY) for u in users]
336
    result = get_entity(payload)
337
    uuids = [entity for (entity, owner) in result]
338

    
339
    existing = []
340
    nonexisting = []
341
    for u in users:
342
        if u.uuid in uuids:
343
            existing.append(u)
344
        else:
345
            nonexisting.append(u)
346
    return (existing, nonexisting)
347

    
348

    
349
def qh_add_quota(serial, sub_list, add_list):
350
    if not QUOTAHOLDER_URL:
351
        return ()
352

    
353
    context = {}
354
    c = get_client()
355

    
356
    sub_quota = []
357
    sub_append = sub_quota.append
358
    add_quota = []
359
    add_append = add_quota.append
360

    
361
    for ql in sub_list:
362
        args = (ql.holder, ql.resource, ENTITY_KEY,
363
                0, ql.capacity, ql.import_limit, ql.export_limit)
364
        sub_append(args)
365

    
366
    for ql in add_list:
367
        args = (ql.holder, ql.resource, ENTITY_KEY,
368
                0, ql.capacity, ql.import_limit, ql.export_limit)
369
        add_append(args)
370

    
371
    result = c.add_quota(context=context,
372
                         clientkey=clientkey,
373
                         serial=serial,
374
                         sub_quota=sub_quota,
375
                         add_quota=add_quota)
376

    
377
    return result
378

    
379

    
380
def qh_query_serials(serials):
381
    if not QUOTAHOLDER_URL:
382
        return ()
383

    
384
    context = {}
385
    c = get_client()
386
    result = c.query_serials(context=context,
387
                             clientkey=clientkey,
388
                             serials=serials)
389
    return result
390

    
391

    
392
def qh_ack_serials(serials):
393
    if not QUOTAHOLDER_URL:
394
        return ()
395

    
396
    context = {}
397
    c = get_client()
398
    result = c.ack_serials(context=context,
399
                           clientkey=clientkey,
400
                           serials=serials)
401
    return
402

    
403
from datetime import datetime
404

    
405
strptime = datetime.strptime
406
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
407

    
408
SECOND_RESOLUTION = 1
409

    
410

    
411
def total_seconds(timedelta_object):
412
    return timedelta_object.seconds + timedelta_object.days * 86400
413

    
414

    
415
def iter_timeline(timeline, before):
416
    if not timeline:
417
        return
418

    
419
    for t in timeline:
420
        yield t
421

    
422
    t = dict(t)
423
    t['issue_time'] = before
424
    yield t
425

    
426

    
427
def _usage_units(timeline, after, before, details=0):
428

    
429
    t_total = 0
430
    uu_total = 0
431
    t_after = strptime(after, timefmt)
432
    t_before = strptime(before, timefmt)
433
    t0 = t_after
434
    u0 = 0
435

    
436
    for point in iter_timeline(timeline, before):
437
        issue_time = point['issue_time']
438

    
439
        if issue_time <= after:
440
            u0 = point['target_allocated_through']
441
            continue
442

    
443
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
444
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
445
        t_total += t_diff
446
        uu_cost = u0 * t_diff
447
        uu_total += uu_cost
448
        t0 = t
449
        u0 = point['target_allocated_through']
450

    
451
        target = point['target']
452
        if details:
453
            yield (target,
454
                   point['resource'],
455
                   point['name'],
456
                   issue_time,
457
                   uu_cost,
458
                   uu_total)
459

    
460
    if not t_total:
461
        return
462

    
463
    yield (target,
464
           'total',
465
           point['resource'],
466
           issue_time,
467
           uu_total / t_total,
468
           uu_total)
469

    
470

    
471
def usage_units(timeline, after, before, details=0):
472
    return list(_usage_units(timeline, after, before, details=details))
473

    
474

    
475
def traffic_units(timeline, after, before, details=0):
476
    tu_total = 0
477
    target = None
478
    issue_time = None
479

    
480
    for point in timeline:
481
        issue_time = point['issue_time']
482
        if issue_time <= after:
483
            continue
484
        if issue_time > before:
485
            break
486

    
487
        target = point['target']
488
        tu = point['target_allocated_through']
489
        tu_total += tu
490

    
491
        if details:
492
            yield (target,
493
                   point['resource'],
494
                   point['name'],
495
                   issue_time,
496
                   tu,
497
                   tu_total)
498

    
499
    if not tu_total:
500
        return
501

    
502
    yield (target,
503
           'total',
504
           point['resource'],
505
           issue_time,
506
           tu_total // len(timeline),
507
           tu_total)
508

    
509

    
510
def timeline_charge(entity, resource, after, before, details, charge_type):
511
    key = '1'
512
    if charge_type == 'charge_usage':
513
        charge_units = usage_units
514
    elif charge_type == 'charge_traffic':
515
        charge_units = traffic_units
516
    else:
517
        m = 'charge type %s not supported' % charge_type
518
        raise ValueError(m)
519

    
520
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
521
    timeline = quotaholder.get_timeline(
522
        context={},
523
        after=after,
524
        before=before,
525
        get_timeline=[[entity, resource, key]])
526
    cu = charge_units(timeline, after, before, details=details)
527
    return cu