Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.6 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
from astakos.quotaholder.callpoint import QuotaholderDjangoDBCallpoint
46
from synnefo.util.number import strbigdec
47
from astakos.im.settings import QUOTAHOLDER_POOLSIZE
48

    
49
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
50

    
51
logger = logging.getLogger(__name__)
52

    
53
inf = float('inf')
54

    
55
clientkey = 'astakos'
56

    
57
_client = None
58

    
59

    
60
def get_client():
61
    global _client
62
    if _client:
63
        return _client
64
    _client = QuotaholderDjangoDBCallpoint()
65
    return _client
66

    
67

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

    
78

    
79
def get_holding(payload):
80
    c = get_client()
81
    if not c:
82
        return
83
    if payload == []:
84
        return []
85
    result = c.get_holding(context={}, get_holding=payload)
86
    logger.debug('get_holding: %s reply: %s' % (payload, result))
87
    return result
88

    
89

    
90
def qh_get_holdings(users, resources):
91
    payload = []
92
    append = payload.append
93
    for user in users:
94
        for resource in resources:
95
            append((user.uuid, resource),)
96
    result = get_holding(payload)
97
    return result
98

    
99

    
100
def quota_limits_per_user_from_get(lst):
101
    quotas = {}
102
    for holder, resource, q, c, imp, exp, ret, rel, flags in lst:
103
        userquotas = quotas.get(holder, {})
104
        userquotas[resource] = QuotaValues(quantity=q, capacity=c,
105
                                           )
106
        quotas[holder] = userquotas
107
    return quotas
108

    
109

    
110
def quotas_per_user_from_get(lst):
111
    limits = {}
112
    counters = {}
113
    for holder, resource, q, c, imp, exp, ret, rel, flags in lst:
114
        userlimits = limits.get(holder, {})
115
        userlimits[resource] = QuotaValues(quantity=q, capacity=c,
116
                                           )
117
        limits[holder] = userlimits
118

    
119
        usercounters = counters.get(holder, {})
120
        usercounters[resource] = QuotaCounters(imported=imp, exported=exp,
121
                                               returned=ret, released=rel)
122
        counters[holder] = usercounters
123
    return limits, counters
124

    
125

    
126
def qh_get_quota(users, resources):
127
    c = get_client()
128
    if not c:
129
        return
130
    payload = []
131
    append = payload.append
132
    for user in users:
133
        for resource in resources:
134
            append((user.uuid, resource),)
135

    
136
    if payload == []:
137
        return []
138
    result = c.get_quota(context={}, get_quota=payload)
139
    logger.debug('get_quota: %s rejected: %s' % (payload, result))
140
    return result
141

    
142

    
143
def qh_get_quota_limits(users, resources):
144
    result = qh_get_quota(users, resources)
145
    return quota_limits_per_user_from_get(result)
146

    
147

    
148
def qh_get_quotas(users, resources):
149
    result = qh_get_quota(users, resources)
150
    return quotas_per_user_from_get(result)
151

    
152

    
153
SetQuotaPayload = namedtuple('SetQuotaPayload', ('holder',
154
                                                 'resource',
155
                                                 'quantity',
156
                                                 'capacity',
157
                                                 'flags'))
158

    
159
QuotaLimits = namedtuple('QuotaLimits', ('holder',
160
                                         'resource',
161
                                         'capacity',
162
                                         ))
163

    
164
QuotaCounters = namedtuple('QuotaCounters', ('imported',
165
                                             'exported',
166
                                             'returned',
167
                                             'released'))
168

    
169

    
170
class QuotaValues(namedtuple('QuotaValues', ('quantity',
171
                                             'capacity',
172
                                             ))):
173
    __slots__ = ()
174

    
175
    def __dir__(self):
176
            return ['quantity', 'capacity']
177

    
178
    def __str__(self):
179
        return '\t'.join(['%s=%s' % (f, strbigdec(getattr(self, f)))
180
                          for f in dir(self)])
181

    
182

    
183
def add_quota_values(q1, q2):
184
    return QuotaValues(
185
        quantity = q1.quantity + q2.quantity,
186
        capacity = q1.capacity + q2.capacity,
187
        )
188

    
189

    
190
def register_quotas(quotas):
191
    if not quotas:
192
        return
193

    
194
    payload = []
195
    append = payload.append
196
    for uuid, userquotas in quotas.iteritems():
197
        for resource, q in userquotas.iteritems():
198
            append(SetQuotaPayload(
199
                    holder=uuid,
200
                    resource=resource,
201
                    quantity=q.quantity,
202
                    capacity=q.capacity,
203
                    flags=0))
204
    return set_quota(payload)
205

    
206

    
207
def send_quotas(userquotas):
208
    if not userquotas:
209
        return
210

    
211
    payload = []
212
    append = payload.append
213
    for holder, quotas in userquotas.iteritems():
214
        for resource, q in quotas.iteritems():
215
            append(SetQuotaPayload(
216
                    holder=holder,
217
                    resource=resource,
218
                    quantity=q.quantity,
219
                    capacity=q.capacity,
220
                    flags=0))
221
    return set_quota(payload)
222

    
223

    
224
def register_resources(resources):
225

    
226
    payload = list(SetQuotaPayload(
227
            holder=resource.service,
228
            resource=resource,
229
            quantity=QH_PRACTICALLY_INFINITE,
230
            capacity=QH_PRACTICALLY_INFINITE,
231
            flags=0) for resource in resources)
232
    return set_quota(payload)
233

    
234

    
235
def qh_add_quota(sub_list, add_list):
236
    context = {}
237
    c = get_client()
238

    
239
    sub_quota = []
240
    sub_append = sub_quota.append
241
    add_quota = []
242
    add_append = add_quota.append
243

    
244
    for ql in sub_list:
245
        args = (ql.holder, ql.resource,
246
                0, ql.capacity)
247
        sub_append(args)
248

    
249
    for ql in add_list:
250
        args = (ql.holder, ql.resource,
251
                0, ql.capacity)
252
        add_append(args)
253

    
254
    result = c.add_quota(context=context,
255
                         sub_quota=sub_quota,
256
                         add_quota=add_quota)
257

    
258
    return result
259

    
260

    
261
from datetime import datetime
262

    
263
strptime = datetime.strptime
264
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
265

    
266
SECOND_RESOLUTION = 1
267

    
268

    
269
def total_seconds(timedelta_object):
270
    return timedelta_object.seconds + timedelta_object.days * 86400
271

    
272

    
273
def iter_timeline(timeline, before):
274
    if not timeline:
275
        return
276

    
277
    for t in timeline:
278
        yield t
279

    
280
    t = dict(t)
281
    t['issue_time'] = before
282
    yield t
283

    
284

    
285
def _usage_units(timeline, after, before, details=0):
286

    
287
    t_total = 0
288
    uu_total = 0
289
    t_after = strptime(after, timefmt)
290
    t_before = strptime(before, timefmt)
291
    t0 = t_after
292
    u0 = 0
293

    
294
    for point in iter_timeline(timeline, before):
295
        issue_time = point['issue_time']
296

    
297
        if issue_time <= after:
298
            u0 = point['target_allocated_through']
299
            continue
300

    
301
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
302
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
303
        t_total += t_diff
304
        uu_cost = u0 * t_diff
305
        uu_total += uu_cost
306
        t0 = t
307
        u0 = point['target_allocated_through']
308

    
309
        target = point['target']
310
        if details:
311
            yield (target,
312
                   point['resource'],
313
                   point['name'],
314
                   issue_time,
315
                   uu_cost,
316
                   uu_total)
317

    
318
    if not t_total:
319
        return
320

    
321
    yield (target,
322
           'total',
323
           point['resource'],
324
           issue_time,
325
           uu_total / t_total,
326
           uu_total)
327

    
328

    
329
def usage_units(timeline, after, before, details=0):
330
    return list(_usage_units(timeline, after, before, details=details))
331

    
332

    
333
def traffic_units(timeline, after, before, details=0):
334
    tu_total = 0
335
    target = None
336
    issue_time = None
337

    
338
    for point in timeline:
339
        issue_time = point['issue_time']
340
        if issue_time <= after:
341
            continue
342
        if issue_time > before:
343
            break
344

    
345
        target = point['target']
346
        tu = point['target_allocated_through']
347
        tu_total += tu
348

    
349
        if details:
350
            yield (target,
351
                   point['resource'],
352
                   point['name'],
353
                   issue_time,
354
                   tu,
355
                   tu_total)
356

    
357
    if not tu_total:
358
        return
359

    
360
    yield (target,
361
           'total',
362
           point['resource'],
363
           issue_time,
364
           tu_total // len(timeline),
365
           tu_total)
366

    
367

    
368
def timeline_charge(entity, resource, after, before, details, charge_type):
369
    if charge_type == 'charge_usage':
370
        charge_units = usage_units
371
    elif charge_type == 'charge_traffic':
372
        charge_units = traffic_units
373
    else:
374
        m = 'charge type %s not supported' % charge_type
375
        raise ValueError(m)
376

    
377
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN,
378
                                    poolsize=QUOTAHOLDER_POOLSIZE)
379
    timeline = quotaholder.get_timeline(
380
        context={},
381
        after=after,
382
        before=before,
383
        get_timeline=[[entity, resource]])
384
    cu = charge_units(timeline, after, before, details=details)
385
    return cu