Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.7 kB)

1 fc1e2f02 Sofia Papagiannaki
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 5ce3ce4f Sofia Papagiannaki
#
3 fc1e2f02 Sofia Papagiannaki
# Redistribution and use in source and binary forms, with or
4 fc1e2f02 Sofia Papagiannaki
# without modification, are permitted provided that the following
5 fc1e2f02 Sofia Papagiannaki
# conditions are met:
6 5ce3ce4f Sofia Papagiannaki
#
7 fc1e2f02 Sofia Papagiannaki
#   1. Redistributions of source code must retain the above
8 fc1e2f02 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
9 fc1e2f02 Sofia Papagiannaki
#      disclaimer.
10 5ce3ce4f Sofia Papagiannaki
#
11 fc1e2f02 Sofia Papagiannaki
#   2. Redistributions in binary form must reproduce the above
12 fc1e2f02 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
13 fc1e2f02 Sofia Papagiannaki
#      disclaimer in the documentation and/or other materials
14 fc1e2f02 Sofia Papagiannaki
#      provided with the distribution.
15 5ce3ce4f Sofia Papagiannaki
#
16 fc1e2f02 Sofia Papagiannaki
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 fc1e2f02 Sofia Papagiannaki
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 fc1e2f02 Sofia Papagiannaki
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 fc1e2f02 Sofia Papagiannaki
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 fc1e2f02 Sofia Papagiannaki
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 fc1e2f02 Sofia Papagiannaki
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 fc1e2f02 Sofia Papagiannaki
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 fc1e2f02 Sofia Papagiannaki
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 fc1e2f02 Sofia Papagiannaki
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 fc1e2f02 Sofia Papagiannaki
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 fc1e2f02 Sofia Papagiannaki
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 fc1e2f02 Sofia Papagiannaki
# POSSIBILITY OF SUCH DAMAGE.
28 5ce3ce4f Sofia Papagiannaki
#
29 fc1e2f02 Sofia Papagiannaki
# The views and conclusions contained in the software and
30 fc1e2f02 Sofia Papagiannaki
# documentation are those of the authors and should not be
31 fc1e2f02 Sofia Papagiannaki
# interpreted as representing official policies, either expressed
32 fc1e2f02 Sofia Papagiannaki
# or implied, of GRNET S.A.
33 fc1e2f02 Sofia Papagiannaki
34 fc1e2f02 Sofia Papagiannaki
import logging
35 bd4f356c Sofia Papagiannaki
import itertools
36 bd4f356c Sofia Papagiannaki
37 bd4f356c Sofia Papagiannaki
from functools import wraps
38 4fc7d569 Sofia Papagiannaki
from collections import namedtuple
39 fc1e2f02 Sofia Papagiannaki
40 fc1e2f02 Sofia Papagiannaki
from django.utils.translation import ugettext as _
41 fc1e2f02 Sofia Papagiannaki
42 30d92d1e Georgios D. Tsoukalas
from astakos.im.settings import (
43 0ea1f32b Georgios D. Tsoukalas
        QUOTAHOLDER_URL, QUOTAHOLDER_TOKEN, LOGGING_LEVEL)
44 fc1e2f02 Sofia Papagiannaki
45 30d92d1e Georgios D. Tsoukalas
if QUOTAHOLDER_URL:
46 c836d69f Georgios D. Tsoukalas
    from kamaki.clients.quotaholder import QuotaholderClient
47 6f00ed3e Sofia Papagiannaki
    from kamaki.clients.quotaholder import QH_PRACTICALLY_INFINITE
48 8b59b8ea Sofia Papagiannaki
49 fc1e2f02 Sofia Papagiannaki
ENTITY_KEY = '1'
50 fc1e2f02 Sofia Papagiannaki
51 ae497612 Olga Brani
52 fc1e2f02 Sofia Papagiannaki
logger = logging.getLogger(__name__)
53 fc1e2f02 Sofia Papagiannaki
54 ae497612 Olga Brani
inf = float('inf')
55 5ce3ce4f Sofia Papagiannaki
56 ee45eb81 Giorgos Korfiatis
clientkey = 'astakos'
57 ee45eb81 Giorgos Korfiatis
58 73fbaec4 Sofia Papagiannaki
_client = None
59 73fbaec4 Sofia Papagiannaki
def get_client():
60 73fbaec4 Sofia Papagiannaki
    global _client
61 73fbaec4 Sofia Papagiannaki
    if _client:
62 73fbaec4 Sofia Papagiannaki
        return _client
63 73fbaec4 Sofia Papagiannaki
    if not QUOTAHOLDER_URL:
64 73fbaec4 Sofia Papagiannaki
        return
65 73fbaec4 Sofia Papagiannaki
    _client = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
66 52116521 Sofia Papagiannaki
    return _client
67 73fbaec4 Sofia Papagiannaki
68 8978cfbd Sofia Papagiannaki
def set_quota(payload):
69 8978cfbd Sofia Papagiannaki
    c = get_client()
70 8978cfbd Sofia Papagiannaki
    if not c:
71 8978cfbd Sofia Papagiannaki
        return
72 8978cfbd Sofia Papagiannaki
    result = c.set_quota(context={}, clientkey=clientkey, set_quota=payload)
73 8978cfbd Sofia Papagiannaki
    logger.info('set_quota: %s rejected: %s' % (payload, result))
74 8978cfbd Sofia Papagiannaki
    return result
75 8978cfbd Sofia Papagiannaki
76 0514bcc7 Giorgos Korfiatis
def qh_get_quota(user, resources):
77 8978cfbd Sofia Papagiannaki
    c = get_client()
78 8978cfbd Sofia Papagiannaki
    if not c:
79 8978cfbd Sofia Papagiannaki
        return
80 16cfac45 Sofia Papagiannaki
    payload = []
81 16cfac45 Sofia Papagiannaki
    append = payload.append
82 0514bcc7 Giorgos Korfiatis
    for r in resources:
83 16cfac45 Sofia Papagiannaki
        append((user.uuid, r, ENTITY_KEY),)
84 8978cfbd Sofia Papagiannaki
    result = c.get_quota(context={}, clientkey=clientkey, get_quota=payload)
85 8978cfbd Sofia Papagiannaki
    logger.info('get_quota: %s rejected: %s' % (payload, result))
86 8978cfbd Sofia Papagiannaki
    return result
87 8978cfbd Sofia Papagiannaki
88 8978cfbd Sofia Papagiannaki
def create_entity(payload):
89 8978cfbd Sofia Papagiannaki
    c = get_client()
90 8978cfbd Sofia Papagiannaki
    if not c:
91 8978cfbd Sofia Papagiannaki
        return
92 6c997921 Sofia Papagiannaki
    result = c.create_entity(
93 6c997921 Sofia Papagiannaki
        context={}, clientkey=clientkey, create_entity=payload)
94 8978cfbd Sofia Papagiannaki
    logger.info('create_entity: %s rejected: %s' % (payload, result))
95 8978cfbd Sofia Papagiannaki
    return result
96 5ce3ce4f Sofia Papagiannaki
97 8978cfbd Sofia Papagiannaki
SetQuotaPayload = namedtuple('SetQuotaPayload', ('holder',
98 8978cfbd Sofia Papagiannaki
                                                 'resource',
99 8978cfbd Sofia Papagiannaki
                                                 'key',
100 8978cfbd Sofia Papagiannaki
                                                 'quantity',
101 8978cfbd Sofia Papagiannaki
                                                 'capacity',
102 8978cfbd Sofia Papagiannaki
                                                 'import_limit',
103 8978cfbd Sofia Papagiannaki
                                                 'export_limit',
104 8978cfbd Sofia Papagiannaki
                                                 'flags'))
105 8978cfbd Sofia Papagiannaki
106 8978cfbd Sofia Papagiannaki
GetQuotaPayload = namedtuple('GetQuotaPayload', ('holder',
107 8978cfbd Sofia Papagiannaki
                                                 'resource',
108 8978cfbd Sofia Papagiannaki
                                                 'key'))
109 8978cfbd Sofia Papagiannaki
110 8978cfbd Sofia Papagiannaki
CreateEntityPayload = namedtuple('CreateEntityPayload', ('entity',
111 16cfac45 Sofia Papagiannaki
                                                         'owner',
112 16cfac45 Sofia Papagiannaki
                                                         'key',
113 16cfac45 Sofia Papagiannaki
                                                         'ownerkey'))
114 2a97d93b Giorgos Korfiatis
QuotaLimits = namedtuple('QuotaLimits', ('holder',
115 5cfd4acb Sofia Papagiannaki
                                         'resource',
116 2a97d93b Giorgos Korfiatis
                                         'capacity',
117 2a97d93b Giorgos Korfiatis
                                         'import_limit',
118 2a97d93b Giorgos Korfiatis
                                         'export_limit'))
119 2a97d93b Giorgos Korfiatis
120 0514bcc7 Giorgos Korfiatis
QuotaValues = namedtuple('QuotaValues', ('quantity',
121 0514bcc7 Giorgos Korfiatis
                                         'capacity',
122 0514bcc7 Giorgos Korfiatis
                                         'import_limit',
123 0514bcc7 Giorgos Korfiatis
                                         'export_limit'))
124 0514bcc7 Giorgos Korfiatis
125 0514bcc7 Giorgos Korfiatis
def add_quota_values(q1, q2):
126 0514bcc7 Giorgos Korfiatis
    return QuotaValues(
127 0514bcc7 Giorgos Korfiatis
        quantity = q1.quantity + q2.quantity,
128 0514bcc7 Giorgos Korfiatis
        capacity = q1.capacity + q2.capacity,
129 0514bcc7 Giorgos Korfiatis
        import_limit = q1.import_limit + q2.import_limit,
130 0514bcc7 Giorgos Korfiatis
        export_limit = q1.export_limit + q2.export_limit)
131 0514bcc7 Giorgos Korfiatis
132 b4be4eee Giorgos Korfiatis
def qh_register_user(user):
133 b4be4eee Giorgos Korfiatis
    return register_users([user])
134 b4be4eee Giorgos Korfiatis
135 8978cfbd Sofia Papagiannaki
def register_users(users):
136 0514bcc7 Giorgos Korfiatis
    rejected = create_users(users)
137 0514bcc7 Giorgos Korfiatis
    if not rejected:
138 0514bcc7 Giorgos Korfiatis
        register_quotas(users)
139 0514bcc7 Giorgos Korfiatis
140 0514bcc7 Giorgos Korfiatis
def create_users(users):
141 8978cfbd Sofia Papagiannaki
    payload = list(CreateEntityPayload(
142 8978cfbd Sofia Papagiannaki
                    entity=u.uuid,
143 8978cfbd Sofia Papagiannaki
                    owner='system',
144 8978cfbd Sofia Papagiannaki
                    key=ENTITY_KEY,
145 8978cfbd Sofia Papagiannaki
                    ownerkey='') for u in users)
146 0514bcc7 Giorgos Korfiatis
    return create_entity(payload)
147 8978cfbd Sofia Papagiannaki
148 0514bcc7 Giorgos Korfiatis
def register_quotas(users):
149 0514bcc7 Giorgos Korfiatis
    payload = []
150 0514bcc7 Giorgos Korfiatis
    append = payload.append
151 0514bcc7 Giorgos Korfiatis
    for u in users:
152 0514bcc7 Giorgos Korfiatis
        for resource, q in u.all_quotas().iteritems():
153 0514bcc7 Giorgos Korfiatis
            append( SetQuotaPayload(
154 0514bcc7 Giorgos Korfiatis
                    holder=u.uuid,
155 0514bcc7 Giorgos Korfiatis
                    resource=resource,
156 0514bcc7 Giorgos Korfiatis
                    key=ENTITY_KEY,
157 0514bcc7 Giorgos Korfiatis
                    quantity=q.quantity,
158 0514bcc7 Giorgos Korfiatis
                    capacity=q.capacity,
159 0514bcc7 Giorgos Korfiatis
                    import_limit=q.import_limit,
160 0514bcc7 Giorgos Korfiatis
                    export_limit=q.export_limit,
161 0514bcc7 Giorgos Korfiatis
                    flags=0))
162 0514bcc7 Giorgos Korfiatis
    return set_quota(payload)
163 0514bcc7 Giorgos Korfiatis
164 0514bcc7 Giorgos Korfiatis
def register_services(services):
165 8978cfbd Sofia Papagiannaki
    payload = list(CreateEntityPayload(
166 8978cfbd Sofia Papagiannaki
                    entity=service,
167 8978cfbd Sofia Papagiannaki
                    owner='system',
168 8978cfbd Sofia Papagiannaki
                    key=ENTITY_KEY,
169 8978cfbd Sofia Papagiannaki
                    ownerkey='') for service in set(services))
170 0514bcc7 Giorgos Korfiatis
    return create_entity(payload)
171 0514bcc7 Giorgos Korfiatis
172 0514bcc7 Giorgos Korfiatis
def register_resources(resources):
173 0514bcc7 Giorgos Korfiatis
    payload = list(SetQuotaPayload(
174 0514bcc7 Giorgos Korfiatis
            holder=resource.service,
175 0514bcc7 Giorgos Korfiatis
            resource=resource,
176 0514bcc7 Giorgos Korfiatis
            key=ENTITY_KEY,
177 0514bcc7 Giorgos Korfiatis
            quantity=QH_PRACTICALLY_INFINITE,
178 0514bcc7 Giorgos Korfiatis
            capacity=0,
179 0514bcc7 Giorgos Korfiatis
            import_limit=QH_PRACTICALLY_INFINITE,
180 0514bcc7 Giorgos Korfiatis
            export_limit=QH_PRACTICALLY_INFINITE,
181 0514bcc7 Giorgos Korfiatis
            flags=0) for resource in resources)
182 0514bcc7 Giorgos Korfiatis
    return set_quota(payload)
183 8978cfbd Sofia Papagiannaki
184 d2b32360 Giorgos Korfiatis
def qh_add_quota(serial, sub_list, add_list):
185 ee45eb81 Giorgos Korfiatis
    if not QUOTAHOLDER_URL:
186 ee45eb81 Giorgos Korfiatis
        return ()
187 ee45eb81 Giorgos Korfiatis
188 ee45eb81 Giorgos Korfiatis
    context = {}
189 ee45eb81 Giorgos Korfiatis
    c = get_client()
190 4319c408 Kostas Papadimitriou
191 d2b32360 Giorgos Korfiatis
    sub_quota = []
192 d2b32360 Giorgos Korfiatis
    sub_append = sub_quota.append
193 d2b32360 Giorgos Korfiatis
    add_quota = []
194 d2b32360 Giorgos Korfiatis
    add_append = add_quota.append
195 d2b32360 Giorgos Korfiatis
196 5cfd4acb Sofia Papagiannaki
    for ql in sub_list:
197 d2b32360 Giorgos Korfiatis
        args = (ql.holder, ql.resource, ENTITY_KEY,
198 d2b32360 Giorgos Korfiatis
                0, ql.capacity, ql.import_limit, ql.export_limit)
199 d2b32360 Giorgos Korfiatis
        sub_append(args)
200 d2b32360 Giorgos Korfiatis
201 5cfd4acb Sofia Papagiannaki
    for ql in add_list:
202 2a97d93b Giorgos Korfiatis
        args = (ql.holder, ql.resource, ENTITY_KEY,
203 2a97d93b Giorgos Korfiatis
                0, ql.capacity, ql.import_limit, ql.export_limit)
204 d2b32360 Giorgos Korfiatis
        add_append(args)
205 2a97d93b Giorgos Korfiatis
206 ee45eb81 Giorgos Korfiatis
    result = c.add_quota(context=context,
207 ee45eb81 Giorgos Korfiatis
                         clientkey=clientkey,
208 ee45eb81 Giorgos Korfiatis
                         serial=serial,
209 d2b32360 Giorgos Korfiatis
                         sub_quota=sub_quota,
210 d2b32360 Giorgos Korfiatis
                         add_quota=add_quota)
211 ee45eb81 Giorgos Korfiatis
212 ee45eb81 Giorgos Korfiatis
    return result
213 ee45eb81 Giorgos Korfiatis
214 ee45eb81 Giorgos Korfiatis
def qh_query_serials(serials):
215 ee45eb81 Giorgos Korfiatis
    if not QUOTAHOLDER_URL:
216 ee45eb81 Giorgos Korfiatis
        return ()
217 ee45eb81 Giorgos Korfiatis
218 ee45eb81 Giorgos Korfiatis
    context = {}
219 ee45eb81 Giorgos Korfiatis
    c = get_client()
220 ee45eb81 Giorgos Korfiatis
    result = c.query_serials(context=context,
221 ee45eb81 Giorgos Korfiatis
                             clientkey=clientkey,
222 ee45eb81 Giorgos Korfiatis
                             serials=serials)
223 ee45eb81 Giorgos Korfiatis
    return result
224 ee45eb81 Giorgos Korfiatis
225 ee45eb81 Giorgos Korfiatis
def qh_ack_serials(serials):
226 ee45eb81 Giorgos Korfiatis
    if not QUOTAHOLDER_URL:
227 ee45eb81 Giorgos Korfiatis
        return ()
228 ee45eb81 Giorgos Korfiatis
229 ee45eb81 Giorgos Korfiatis
    context = {}
230 ee45eb81 Giorgos Korfiatis
    c = get_client()
231 ee45eb81 Giorgos Korfiatis
    result = c.ack_serials(context=context,
232 ee45eb81 Giorgos Korfiatis
                           clientkey=clientkey,
233 ee45eb81 Giorgos Korfiatis
                           serials=serials)
234 ee45eb81 Giorgos Korfiatis
    return
235 5ce3ce4f Sofia Papagiannaki
236 2925e285 root
from datetime import datetime
237 2925e285 root
238 2925e285 root
strptime = datetime.strptime
239 2925e285 root
timefmt = '%Y-%m-%dT%H:%M:%S.%f'
240 2925e285 root
241 476fdba1 root
SECOND_RESOLUTION = 1
242 476fdba1 root
243 9a06d96f Olga Brani
244 2925e285 root
def total_seconds(timedelta_object):
245 2925e285 root
    return timedelta_object.seconds + timedelta_object.days * 86400
246 2925e285 root
247 9a06d96f Olga Brani
248 476fdba1 root
def iter_timeline(timeline, before):
249 476fdba1 root
    if not timeline:
250 476fdba1 root
        return
251 476fdba1 root
252 476fdba1 root
    for t in timeline:
253 476fdba1 root
        yield t
254 476fdba1 root
255 476fdba1 root
    t = dict(t)
256 476fdba1 root
    t['issue_time'] = before
257 476fdba1 root
    yield t
258 476fdba1 root
259 9a06d96f Olga Brani
260 476fdba1 root
def _usage_units(timeline, after, before, details=0):
261 476fdba1 root
262 2925e285 root
    t_total = 0
263 476fdba1 root
    uu_total = 0
264 476fdba1 root
    t_after = strptime(after, timefmt)
265 476fdba1 root
    t_before = strptime(before, timefmt)
266 476fdba1 root
    t0 = t_after
267 476fdba1 root
    u0 = 0
268 2925e285 root
269 476fdba1 root
    for point in iter_timeline(timeline, before):
270 2925e285 root
        issue_time = point['issue_time']
271 2925e285 root
272 476fdba1 root
        if issue_time <= after:
273 476fdba1 root
            u0 = point['target_allocated_through']
274 2925e285 root
            continue
275 2925e285 root
276 476fdba1 root
        t = strptime(issue_time, timefmt) if issue_time <= before else t_before
277 476fdba1 root
        t_diff = int(total_seconds(t - t0) * SECOND_RESOLUTION)
278 2925e285 root
        t_total += t_diff
279 2925e285 root
        uu_cost = u0 * t_diff
280 2925e285 root
        uu_total += uu_cost
281 476fdba1 root
        t0 = t
282 476fdba1 root
        u0 = point['target_allocated_through']
283 2925e285 root
284 476fdba1 root
        target = point['target']
285 2925e285 root
        if details:
286 2925e285 root
            yield  (target,
287 2925e285 root
                    point['resource'],
288 2925e285 root
                    point['name'],
289 2925e285 root
                    issue_time,
290 2925e285 root
                    uu_cost,
291 2925e285 root
                    uu_total)
292 2925e285 root
293 2925e285 root
    if not t_total:
294 2925e285 root
        return
295 2925e285 root
296 2925e285 root
    yield  (target,
297 2925e285 root
            'total',
298 2925e285 root
            point['resource'],
299 2925e285 root
            issue_time,
300 9a06d96f Olga Brani
            uu_total / t_total,
301 2925e285 root
            uu_total)
302 2925e285 root
303 9a06d96f Olga Brani
304 476fdba1 root
def usage_units(timeline, after, before, details=0):
305 476fdba1 root
    return list(_usage_units(timeline, after, before, details=details))
306 2925e285 root
307 9a06d96f Olga Brani
308 476fdba1 root
def traffic_units(timeline, after, before, details=0):
309 82b05401 root
    tu_total = 0
310 82b05401 root
    target = None
311 82b05401 root
    issue_time = None
312 82b05401 root
313 82b05401 root
    for point in timeline:
314 82b05401 root
        issue_time = point['issue_time']
315 476fdba1 root
        if issue_time <= after:
316 476fdba1 root
            continue
317 476fdba1 root
        if issue_time > before:
318 476fdba1 root
            break
319 476fdba1 root
320 476fdba1 root
        target = point['target']
321 82b05401 root
        tu = point['target_allocated_through']
322 82b05401 root
        tu_total += tu
323 82b05401 root
324 82b05401 root
        if details:
325 82b05401 root
            yield  (target,
326 82b05401 root
                    point['resource'],
327 82b05401 root
                    point['name'],
328 82b05401 root
                    issue_time,
329 82b05401 root
                    tu,
330 82b05401 root
                    tu_total)
331 82b05401 root
332 82b05401 root
    if not tu_total:
333 82b05401 root
        return
334 82b05401 root
335 82b05401 root
    yield  (target,
336 82b05401 root
            'total',
337 82b05401 root
            point['resource'],
338 82b05401 root
            issue_time,
339 9a06d96f Olga Brani
            tu_total // len(timeline),
340 82b05401 root
            tu_total)
341 2925e285 root
342 9a06d96f Olga Brani
343 2925e285 root
def timeline_charge(entity, resource, after, before, details, charge_type):
344 2925e285 root
    key = '1'
345 2925e285 root
    if charge_type == 'charge_usage':
346 2925e285 root
        charge_units = usage_units
347 2925e285 root
    elif charge_type == 'charge_traffic':
348 2925e285 root
        charge_units = traffic_units
349 2925e285 root
    else:
350 2925e285 root
        m = 'charge type %s not supported' % charge_type
351 2925e285 root
        raise ValueError(m)
352 2925e285 root
353 30d92d1e Georgios D. Tsoukalas
    quotaholder = QuotaholderClient(QUOTAHOLDER_URL, token=QUOTAHOLDER_TOKEN)
354 2925e285 root
    timeline = quotaholder.get_timeline(
355 9a06d96f Olga Brani
        context={},
356 9a06d96f Olga Brani
        after=after,
357 9a06d96f Olga Brani
        before=before,
358 9a06d96f Olga Brani
        get_timeline=[[entity, resource, key]])
359 476fdba1 root
    cu = charge_units(timeline, after, before, details=details)
360 82b05401 root
    return cu