root / commissioning / hlapi / api.py @ eb5e92f4
History | View | Annotate | Download (32.9 kB)
1 |
from util import check_abs_name |
---|---|
2 |
from util import check_node_key |
3 |
from util import is_system_node |
4 |
from util import check_context |
5 |
from util import check_string |
6 |
from util import NameOfSystemNode |
7 |
from util import NameOfResourcesNode |
8 |
from util import NameOfGroupsNode |
9 |
from util import NameOfUsersNode |
10 |
from util import make_abs_resource_name |
11 |
from util import make_abs_group_name |
12 |
from util import make_abs_user_name |
13 |
from util import reparent_child_name_under |
14 |
from util import check_abs_resource_name |
15 |
from util import check_abs_group_name |
16 |
from util import parent_abs_name_of |
17 |
from util import relative_child_name_under |
18 |
from util import make_rel_global_resource_name |
19 |
from util import make_rel_group_name |
20 |
from util import make_rel_user_name |
21 |
from util import check_name |
22 |
from util import check_attribute_name |
23 |
from util import level_of_node |
24 |
from util import check_abs_name |
25 |
from util import check_relative_name |
26 |
from util import is_abs_resource_name |
27 |
from util import is_abs_group_name |
28 |
from util import is_abs_user_name |
29 |
|
30 |
|
31 |
class Quota(object): |
32 |
def __init__(self): |
33 |
self.entity = None |
34 |
self.resource = None |
35 |
self.quantity = None |
36 |
self.capacity = None |
37 |
self.import_limit = None |
38 |
self.export_limit = None |
39 |
self.imported = None |
40 |
self.exported = None |
41 |
self.returned = None |
42 |
self.released = None |
43 |
self.flags = Quota.default_flags()
|
44 |
self.current_amount = self.__current_amount() |
45 |
|
46 |
|
47 |
def __current_amount(self): |
48 |
def num(x): |
49 |
if x is None: |
50 |
return 0 |
51 |
else:
|
52 |
return x
|
53 |
|
54 |
return num(self.quantity) + \ |
55 |
num(self.imported) - \
|
56 |
num(self.exported) + \
|
57 |
num(self.returned) - \
|
58 |
num(self.released)
|
59 |
|
60 |
# def is_unknown(self):
|
61 |
# return \
|
62 |
# self.entity is None and \
|
63 |
# self.resource is None and \
|
64 |
# self.quantity is None and \
|
65 |
# self.capacity is None and \
|
66 |
# self.import_limit is None and \
|
67 |
# self.export_limit is None and \
|
68 |
# self.returned is None and \
|
69 |
# self.released is None and \
|
70 |
# (self.flags is None or self.flags == 0)
|
71 |
|
72 |
@classmethod
|
73 |
def default_flags(cls): |
74 |
return 0 |
75 |
|
76 |
@classmethod
|
77 |
def from_tuple(cls, quota): |
78 |
q = Quota() |
79 |
q.entity = quota[0]
|
80 |
q.resource = quota[1]
|
81 |
q.quantity = quota[2]
|
82 |
q.capacity = quota[3]
|
83 |
q.import_limit = quota[4]
|
84 |
q.export_limit = quota[5]
|
85 |
q.imported = quota[6]
|
86 |
q.exported = quota[7]
|
87 |
q.returned = quota[8]
|
88 |
q.released = quota[9]
|
89 |
q.current_amount = q.__current_amount() |
90 |
q.flags = quota[10]
|
91 |
return q
|
92 |
|
93 |
@classmethod
|
94 |
def from_dict(cls, d): |
95 |
q = Quota() |
96 |
q.entity = d.get('entity') or None |
97 |
q.resource = d.get('resource') or None |
98 |
q.quantity = d.get('quantity') or None |
99 |
q.capacity = d.get('capacity') or None |
100 |
q.import_limit = d.get('import_limit') or None |
101 |
q.export_limit = d.get('export_limit') or None |
102 |
q.imported = d.get('imported') or None |
103 |
q.exported = d.get('exported') or None |
104 |
q.returned = d.get('returned') or None |
105 |
q.released = d.get('released') or None |
106 |
q.current_amount = q.__current_amount() |
107 |
q.flags = d.get('flags') or Quota.default_flags() |
108 |
return q
|
109 |
|
110 |
def to_full_dict(self): |
111 |
return {'entity': self.entity, |
112 |
'resource': self.resource, |
113 |
'quantity': self.quantity, |
114 |
'capacity': self.capacity, |
115 |
'import_limit': self.import_limit, |
116 |
'export_limit': self.export_limit, |
117 |
'imported': self.imported, |
118 |
'exported': self.exported, |
119 |
'returned': self.returned, |
120 |
'released': self.released, |
121 |
'current_amount': self.current_amount, |
122 |
'flags': self.flags |
123 |
} |
124 |
|
125 |
def to_dict(self): |
126 |
# d={}
|
127 |
# def add(key, value):
|
128 |
# if value is not None:
|
129 |
# d[key] = value
|
130 |
# add('entity', self.entity)
|
131 |
# add('resource', self.resource)
|
132 |
# add('quantity', self.quantity)
|
133 |
# add('capacity', self.capacity)
|
134 |
# add('import_limit', self.import_limit)
|
135 |
# add('export_limit', self.export_limit)
|
136 |
# add('imported', self.imported)
|
137 |
# add('exported', self.exported)
|
138 |
# add('returned', self.returned)
|
139 |
# add('released', self.released)
|
140 |
# add('current_amount', self.current_amount)
|
141 |
# if (self.flags is not None) and (self.flags != 0):
|
142 |
# d['flags'] = self.flags
|
143 |
# return d
|
144 |
return self.to_full_dict() |
145 |
|
146 |
|
147 |
def get_attribute_value(self): |
148 |
# We always use capacity as the value of an attribute
|
149 |
return self.capacity |
150 |
|
151 |
def __str__(self): |
152 |
return str(self.to_dict()) |
153 |
|
154 |
def __repr__(self): |
155 |
return self.__str__() |
156 |
|
157 |
class HighLevelAPI(object): |
158 |
"""
|
159 |
High-level Quota Holder API that supports definitions of resources,
|
160 |
groups and users and the transfer of resource quotas from
|
161 |
respective resources to both groups and users.
|
162 |
|
163 |
Internally it uses the low-level Quota Holder API, so it maps everything
|
164 |
into Entities, Resources, Policies and Limits.
|
165 |
|
166 |
High-level API Terminology
|
167 |
~~~~~~~~~~~~~~
|
168 |
|
169 |
An absolute name is a name that is either 'system' or contains 'system/' as
|
170 |
its prefix. It is made of parts separated by slash '/'. It does never
|
171 |
end in a '/' and it does not contain consecutive '/'s. So, 'system/groups'
|
172 |
and 'system/resources' are absolute names but 'foo', 'foo/bar',
|
173 |
'system//groups' and 'system/groups/' are not absolute names.
|
174 |
|
175 |
A node is an entity whose name is an absolute name, as defined above.
|
176 |
The root node named 'system' is always present and is enforced by the
|
177 |
low-level API. This node is called the system node or, symbolically,
|
178 |
SYSTEM.
|
179 |
A node is uniquely identified by its absolute name.
|
180 |
|
181 |
The level of a node is the number of slashes it contains. Equivalently, it
|
182 |
is the number of its parts minus one. So, SYSTEM has level zero,
|
183 |
'system/resources' has level one, 'system/resources/pithos+' has level
|
184 |
two and so on. For a node with a symbolic name A, its level is given
|
185 |
symbolically as level(A). By definition level(SYSTEM) = 0.
|
186 |
We subsequently use upper-case letters or words, like SYSTEM, A, B and
|
187 |
so on, in order to identify nodes.
|
188 |
|
189 |
A node A which is not SYSTEM is the (direct) parent of node B
|
190 |
if and only if level(A) + 1 = level(B). We denote parenthood symbolically
|
191 |
by writing parent(B) = A. Also, we say that B is a (direct) child of A.
|
192 |
By definition, parent(SYSTEM) = SYSTEM.
|
193 |
|
194 |
A top-level node is a node if level one. There are three top-level nodes,
|
195 |
namely 'system/resources', 'system/users' and 'system/groups'. The symbolic
|
196 |
names for these are RESOURCES, USERS and GROUPS respectively.
|
197 |
RESOURCES is used to model ~okeanos resources, USERS is used to model
|
198 |
~okeanos users and GROUPS is used to model ~okeanos groups. By the previous
|
199 |
definition of parenthood,
|
200 |
parent(RESOURCES) = parent(USERS) = parent(GROUPS) = SYSTEM.
|
201 |
|
202 |
A (global) resource R is a node such that parent(R) = RESOURCES.
|
203 |
A group G is a node such that parent(G) = GROUPS.
|
204 |
A user U is a node such that parent(U) = USERS.
|
205 |
"""
|
206 |
|
207 |
def __init__(self, qh = None, **kwd): |
208 |
self.__client_key = kwd.get('client_key') or '' |
209 |
self.__node_key = kwd.get('node_key') or '' |
210 |
|
211 |
if qh is None: |
212 |
def new_http_qh(url): |
213 |
from commissioning.clients.http import HTTP_API_Client |
214 |
from commissioning import QuotaholderAPI |
215 |
class QuotaholderHTTP(HTTP_API_Client): |
216 |
api_spec = QuotaholderAPI() |
217 |
return QuotaholderHTTP(url)
|
218 |
|
219 |
def check_dict(d): |
220 |
if d.has_key('QH_URL'): |
221 |
return kwd['QH_URL'] |
222 |
elif d.has_key('QH_HOST') and d.has_key('QH_PORT'): |
223 |
return "http://%s:%s/api/quotaholder/v" % ( |
224 |
d['QH_HOST'],
|
225 |
d['QH_PORT'])
|
226 |
else:
|
227 |
return None |
228 |
|
229 |
from os import environ |
230 |
url = check_dict(kwd) or check_dict(environ)
|
231 |
if url is None: |
232 |
raise Exception("No quota holder low-level api specified") |
233 |
del environ
|
234 |
self.__qh = new_http_qh(url)
|
235 |
|
236 |
else:
|
237 |
self.__qh = qh
|
238 |
|
239 |
self.__context = check_context(kwd.get('context') or {}) |
240 |
self.__node_keys = {
|
241 |
NameOfSystemNode: check_string('system_key',
|
242 |
kwd.get('system_key') or ''), |
243 |
NameOfResourcesNode: check_string('resources_key',
|
244 |
kwd.get('resources_key') or ''), |
245 |
NameOfGroupsNode: check_string('groups_key',
|
246 |
kwd.get('groups_key') or ''), |
247 |
NameOfUsersNode: check_string('users_key',
|
248 |
kwd.get('users_key') or '') |
249 |
} |
250 |
|
251 |
|
252 |
# internal API
|
253 |
def __create_attribute_of_node_for_resource(self, |
254 |
node_name, |
255 |
node_label, |
256 |
resource_name, |
257 |
attribute_name, |
258 |
quantity, |
259 |
capacity, |
260 |
import_limit, |
261 |
export_limit, |
262 |
flags): |
263 |
#
|
264 |
check_abs_name(node_name, node_label) |
265 |
check_attribute_name(attribute_name) |
266 |
|
267 |
abs_resource_name = make_abs_resource_name(resource_name) |
268 |
|
269 |
computed_attribute_name = self.make_full_attribute_name(
|
270 |
attribute_name, |
271 |
node_name, |
272 |
abs_resource_name) |
273 |
|
274 |
return self.set_quota( |
275 |
entity=node_name, |
276 |
resource=computed_attribute_name, |
277 |
quantity=quantity, |
278 |
capacity=capacity, |
279 |
import_limit=import_limit, |
280 |
export_limit=export_limit, |
281 |
flags=flags) |
282 |
|
283 |
|
284 |
#+++##########################################
|
285 |
# Public, low-level API.
|
286 |
# We expose some low-level Quota Holder API
|
287 |
#+++##########################################
|
288 |
|
289 |
def qh_list_entities(self, entity, key=''): |
290 |
key = key or self.__node_keys.get(entity) or '' |
291 |
return self.__qh.list_entities(context=self.__context, |
292 |
entity=entity, |
293 |
key=key) |
294 |
|
295 |
def qh_list_resources(self, entity, key=''): |
296 |
key = key or self.__node_keys.get(entity) or '' |
297 |
return self.__qh.list_resources(context=self.__context, |
298 |
entity=entity, |
299 |
key=key) |
300 |
|
301 |
def qh_get_quota(self, entity, resource, key=''): |
302 |
key = key or self.__node_keys.get(entity) or '' |
303 |
quota_list = self.__qh.get_quota(context=self.__context, |
304 |
get_quota=[(entity, resource, key)]) |
305 |
if quota_list:
|
306 |
quota = quota_list[0]
|
307 |
return Quota.from_tuple(quota)
|
308 |
else:
|
309 |
return Quota() # empty |
310 |
|
311 |
|
312 |
def qh_set_quota(self, entity, resource, key, |
313 |
quantity, capacity, |
314 |
import_limit, export_limit, flags): |
315 |
key = key or self.__node_keys.get(entity) or '' |
316 |
rejected = self.__qh.set_quota(context=self.__context, |
317 |
set_quota=[(entity, |
318 |
resource, |
319 |
key, |
320 |
quantity, |
321 |
capacity, |
322 |
import_limit, |
323 |
export_limit, |
324 |
flags)]) |
325 |
if len(rejected) > 0: |
326 |
raise Exception("Rejected set_quota for entity=%s, resource=%s" % ( |
327 |
entity, resource)) |
328 |
q = Quota.from_tuple((entity, |
329 |
resource, |
330 |
quantity, |
331 |
capacity, |
332 |
import_limit, |
333 |
export_limit, |
334 |
0, 0, 0, 0, |
335 |
flags)) |
336 |
return q
|
337 |
|
338 |
|
339 |
def qh_create_entity(self, |
340 |
entity, |
341 |
owner, |
342 |
key='',
|
343 |
owner_key='',
|
344 |
entity_label='entity'):
|
345 |
key = self.get_cached_node_key(entity, entity_label)
|
346 |
owner_key = self.get_cached_node_key(owner)
|
347 |
|
348 |
rejected = self.__qh.create_entity(context=self.__context, |
349 |
create_entity=[(entity, |
350 |
owner, |
351 |
key, |
352 |
owner_key)]) |
353 |
if len(rejected) > 0: |
354 |
raise Exception("Could not create %s='%s' under '%s'. Probably it already exists?" % ( |
355 |
entity_label, entity, owner)) |
356 |
return entity
|
357 |
|
358 |
|
359 |
def qh_get_entity(self, entity, key=''): |
360 |
key = key or self.__node_keys.get(entity) or '' |
361 |
entity_owners = self.__qh.get_entity(context=self.__context, |
362 |
get_entity=[(entity, key)]) |
363 |
if len(entity_owners) == 0: |
364 |
return None |
365 |
else:
|
366 |
return entity_owners[0] |
367 |
|
368 |
def qh_release_entity(self, entity, key=''): |
369 |
key = key or self.__node_keys.get(entity) or '' |
370 |
rejected = self.__qh.release_entity(context=self.__context, |
371 |
release_entity=[(entity, key)]) |
372 |
if len(rejected) > 0: |
373 |
raise Exception("Could not release entity '%s'" % (entity)) |
374 |
|
375 |
def qh_issue_one_commission(self, target_entity, target_entity_key, |
376 |
owner, owner_key, |
377 |
source_entity, resource, quantity): |
378 |
client_key = self.__client_key
|
379 |
tx_id = self.__qh.issue_commission(
|
380 |
context=self.__context,
|
381 |
target=target_entity, |
382 |
key=target_entity_key, |
383 |
clientkey=client_key, |
384 |
owner=owner, |
385 |
ownerkey=owner_key, |
386 |
provisions=[(source_entity, |
387 |
resource, |
388 |
quantity)]) |
389 |
try:
|
390 |
self.__qh.accept_commission(
|
391 |
context=self.__context,
|
392 |
clientkey=client_key, |
393 |
serials=[tx_id]) |
394 |
return tx_id
|
395 |
except:
|
396 |
self.__qh.reject_commission(
|
397 |
context=self.__context,
|
398 |
clientkey=client_key, |
399 |
serials=[tx_id]) |
400 |
|
401 |
|
402 |
#---##########################################
|
403 |
# Public, low-level API.
|
404 |
# We expose some low-level Quota Holder API
|
405 |
#---##########################################
|
406 |
|
407 |
|
408 |
#+++##########################################
|
409 |
# Public, high-level API.
|
410 |
#+++##########################################
|
411 |
|
412 |
def get_quota(self, entity, resource): |
413 |
check_abs_name(entity, 'entity')
|
414 |
return self.qh_get_quota(entity, |
415 |
resource, |
416 |
self.get_cached_node_key(entity))
|
417 |
|
418 |
|
419 |
def set_quota(self, entity, resource, |
420 |
quantity, capacity, |
421 |
import_limit, export_limit, flags): |
422 |
check_abs_name(entity) |
423 |
return self.qh_set_quota(entity=entity, |
424 |
resource=resource, |
425 |
key=self.get_cached_node_key(entity),
|
426 |
quantity=quantity, |
427 |
capacity=capacity, |
428 |
import_limit=import_limit, |
429 |
export_limit=export_limit, |
430 |
flags=flags) |
431 |
|
432 |
def issue_one_commission(self, target_entity, source_entity, |
433 |
resource, quantity): |
434 |
check_abs_name(target_entity, 'target_entity')
|
435 |
check_abs_name(source_entity, 'source_entity')
|
436 |
|
437 |
# owner=parent_abs_name_of(target_entity, 'parent_target_entity')
|
438 |
# owner_key = self.get_cached_node_key(owner, 'owner')
|
439 |
owner = '' # Ignore the owner here. Everything must be set up OK |
440 |
owner_key = ''
|
441 |
try:
|
442 |
return self.qh_issue_one_commission( |
443 |
target_entity=target_entity, |
444 |
target_entity_key=self.get_cached_node_key(target_entity),
|
445 |
owner=owner, |
446 |
owner_key=owner_key, |
447 |
source_entity=source_entity, |
448 |
resource=resource, |
449 |
quantity=quantity) |
450 |
except Exception, e: |
451 |
raise Exception( |
452 |
"Could not transfer %s from '%s' to '%s' for resource '%s'. Original error is %s: %s" % (
|
453 |
quantity, source_entity, target_entity, resource, |
454 |
type(e).__name__, str(e))) |
455 |
|
456 |
|
457 |
def get_cached_node_key(self, node_name, node_label='node_name'): |
458 |
check_abs_name(node_name, node_label) |
459 |
return self.__node_keys.get(node_name) or self.__node_key or '' |
460 |
|
461 |
|
462 |
def set_cached_node_key(self, node_name, node_key, node_label='node_name'): |
463 |
check_abs_name(node_name, node_label) |
464 |
check_node_key(node_key) |
465 |
if node_key is None: |
466 |
node_key = ''
|
467 |
self.__node_keys[node_name] = node_key
|
468 |
|
469 |
|
470 |
def node_keys(self): |
471 |
return self.__node_keys.copy() # Client cannot mess with the original |
472 |
|
473 |
|
474 |
def get_node_children(self, node_name): |
475 |
check_abs_name(node_name, 'node_name')
|
476 |
all_entities = self.__qh.list_entities(
|
477 |
context=self.__context,
|
478 |
entity=node_name, |
479 |
key=self.get_cached_node_key(node_name))
|
480 |
return [child_node_name for child_node_name in all_entities \ |
481 |
if child_node_name.startswith(node_name + '/')] |
482 |
|
483 |
|
484 |
def get_toplevel_nodes(self): |
485 |
"""
|
486 |
Return all nodes with absolute name starting with 'system/'
|
487 |
"""
|
488 |
return self.get_node_children(NameOfSystemNode) |
489 |
|
490 |
|
491 |
def get_resources(self): |
492 |
self.ensure_resources_node()
|
493 |
return self.get_node_children(NameOfResourcesNode) |
494 |
|
495 |
|
496 |
def get_groups(self): |
497 |
self.ensure_groups_node()
|
498 |
return self.get_node_children(NameOfGroupsNode) |
499 |
|
500 |
|
501 |
def get_users(self): |
502 |
self.ensure_users_node()
|
503 |
return self.get_node_children(NameOfUsersNode) |
504 |
|
505 |
|
506 |
def ensure_node(self, abs_node_name, label='abs_node_name'): |
507 |
if not self.node_exists(abs_node_name, label): |
508 |
return self.create_node(abs_node_name, label) |
509 |
else:
|
510 |
return abs_node_name
|
511 |
|
512 |
|
513 |
def ensure_top_level_nodes(self): |
514 |
return [self.ensure_resources_node(), |
515 |
self.ensure_users_node(),
|
516 |
self.ensure_groups_node()]
|
517 |
|
518 |
|
519 |
def ensure_resources_node(self): |
520 |
"""
|
521 |
Ensure that the node 'system/resources' exists.
|
522 |
"""
|
523 |
return self.ensure_node(NameOfResourcesNode, 'NameOfResourcesNode') |
524 |
|
525 |
|
526 |
def ensure_groups_node(self): |
527 |
"""
|
528 |
Ensure that the node 'system/groups' exists.
|
529 |
"""
|
530 |
return self.ensure_node(NameOfGroupsNode, 'NameOfGroupsNode') |
531 |
|
532 |
|
533 |
def ensure_users_node(self): |
534 |
"""
|
535 |
Ensure that the node 'system/users' exists.
|
536 |
"""
|
537 |
return self.ensure_node(NameOfUsersNode, 'NameOfGroupsNode') |
538 |
|
539 |
|
540 |
def node_exists(self, abs_node_name, label='abs_node_name'): |
541 |
"""
|
542 |
Checks if a node with the absolute name ``abs_node_name`` exists.
|
543 |
|
544 |
Returns ``True``/``False`` accordingly.
|
545 |
"""
|
546 |
check_abs_name(abs_node_name, label) |
547 |
node_key = self.get_cached_node_key(abs_node_name)
|
548 |
entity_owner_list = self.__qh.get_entity(
|
549 |
context=self.__context,
|
550 |
get_entity=[(abs_node_name, node_key)] |
551 |
) |
552 |
return len(entity_owner_list) == 1 # TODO: any other check here? |
553 |
|
554 |
|
555 |
def group_exists(self, group_name): |
556 |
abs_group_name = make_abs_group_name(group_name) |
557 |
return self.node_exists(abs_group_name, 'abs_group_name') |
558 |
|
559 |
|
560 |
def resource_exists(self, resource_name): |
561 |
abs_resource_name = make_abs_resource_name(resource_name) |
562 |
return self.node_exists(abs_resource_name, 'abs_resource_name') |
563 |
|
564 |
|
565 |
def user_exists(self, user_name): |
566 |
abs_user_name = make_abs_user_name(user_name) |
567 |
return self.node_exists(abs_user_name, 'abs_user_name') |
568 |
|
569 |
|
570 |
def create_node(self, node_name, node_label): |
571 |
"""
|
572 |
Creates a node with an absolute name ``node_name``.
|
573 |
|
574 |
If the hierarchy up to ``node_name`` does not exist, then it is
|
575 |
created on demand.
|
576 |
|
577 |
Returns the absolute ``node_name``.
|
578 |
|
579 |
The implementation maps a node to a Quota Holder entity.
|
580 |
"""
|
581 |
# print "Creating %s=%s" % (node_label, node_name)
|
582 |
check_abs_name(node_name, node_label) |
583 |
|
584 |
if is_system_node(node_name):
|
585 |
# SYSTEM always exists
|
586 |
print "SYSTEM exists" |
587 |
return node_name
|
588 |
|
589 |
parent_node_name = parent_abs_name_of(node_name, node_label) |
590 |
# Recursively create hierarchy. Beware the keys must be known.
|
591 |
self.ensure_node(parent_node_name, node_label)
|
592 |
|
593 |
node_key = self.get_cached_node_key(node_name)
|
594 |
parent_node_key = self.get_cached_node_key(parent_node_name)
|
595 |
|
596 |
return self.qh_create_entity(entity=node_name, |
597 |
owner=parent_node_name, |
598 |
key=node_key, |
599 |
owner_key=parent_node_key, |
600 |
entity_label=node_label) |
601 |
|
602 |
|
603 |
def make_full_attribute_name(self, attribute_name, parent_node, for_node=''): |
604 |
if (for_node == '') or (for_node is None): |
605 |
for_node = parent_node |
606 |
check_attribute_name(attribute_name) |
607 |
check_abs_name(parent_node, 'parent_node')
|
608 |
check_abs_name(for_node, 'attribute_for_node')
|
609 |
|
610 |
if parent_node == for_node:
|
611 |
return "%s::%s" % (attribute_name, parent_node) |
612 |
else:
|
613 |
return "%s::%s::%s" % (attribute_name, parent_node, for_node) |
614 |
|
615 |
|
616 |
def split_full_attribute_name(self, name): |
617 |
data = name.split("::")
|
618 |
if len(data) == 2: |
619 |
return (data[0], data[1], data[1]) |
620 |
elif len(data) == 3: |
621 |
return (data[0], data[1], data[2]) |
622 |
else:
|
623 |
raise Exception("Bad form of attribute name: '%s'" % (name)) |
624 |
|
625 |
|
626 |
def get_resource_info(self, name): |
627 |
if not self.resource_exists(name): |
628 |
raise Exception("Unknown resource '%s'" % (name)) |
629 |
|
630 |
abs_resource_name = make_abs_resource_name(name) |
631 |
|
632 |
|
633 |
resource_quota = self.get_quota(entity=abs_resource_name,
|
634 |
resource=abs_resource_name) |
635 |
|
636 |
rdef_user_quota_name = self.make_full_attribute_name(
|
637 |
'user', abs_resource_name)
|
638 |
rdef_user_quota = self.get_quota(
|
639 |
entity=abs_resource_name, resource=rdef_user_quota_name) |
640 |
|
641 |
rdef_user_max_quota_name = self.make_full_attribute_name(
|
642 |
'user_max', abs_resource_name)
|
643 |
rdef_user_max_quota = self.get_quota(
|
644 |
entity=abs_resource_name, resource=rdef_user_max_quota_name) |
645 |
|
646 |
rdef_group_max_quota_name = self.make_full_attribute_name(
|
647 |
'group_max', abs_resource_name)
|
648 |
rdef_group_max_quota = self.get_quota(
|
649 |
entity=abs_resource_name, resource=rdef_group_max_quota_name) |
650 |
|
651 |
return {'resource_quota': resource_quota, |
652 |
'user_quota': rdef_user_quota,
|
653 |
'user_max_quota': rdef_user_max_quota,
|
654 |
'group_max_quota': rdef_group_max_quota}
|
655 |
|
656 |
def define_resource(self, |
657 |
name, |
658 |
total_quota, |
659 |
user_quota, |
660 |
user_max_quota, |
661 |
group_max_quota): |
662 |
"""
|
663 |
Defines a resource globally known to Quota Holder.
|
664 |
|
665 |
Returns the absolute name of the created resource. Note that this may
|
666 |
be different from ``name``.
|
667 |
|
668 |
The implementation maps a global resource to a Quota Holder entity
|
669 |
(so this is equivalent to a node in the high-level API but an extra
|
670 |
check is made to ensure the resource is under 'system/resources').
|
671 |
"""
|
672 |
## TODO: Check total_quota is greater than the other quotas.
|
673 |
## TODO: Check user_max_quota >= user_quota
|
674 |
|
675 |
# If the name is not in absolute form, make it so.
|
676 |
# E.g. if it is 'pithos+' make it 'system/resources/pithos+'
|
677 |
abs_resource_name = make_abs_resource_name(name) |
678 |
|
679 |
# Create the resource node
|
680 |
self.create_node(node_name=abs_resource_name,
|
681 |
node_label='abs_resource_name')
|
682 |
|
683 |
# Associate the node with the resource of the same
|
684 |
# absolute name
|
685 |
resource_quota = self.set_quota(entity=abs_resource_name,
|
686 |
resource=abs_resource_name, |
687 |
quantity=total_quota, |
688 |
capacity=None, # Grow to infinity |
689 |
import_limit=None,
|
690 |
export_limit=None,
|
691 |
flags=0)
|
692 |
|
693 |
rdef_user_quota = self.define_attribute_of_resource(
|
694 |
abs_resource_name=abs_resource_name, |
695 |
attribute_name='user',
|
696 |
quantity=0,
|
697 |
capacity=user_quota, |
698 |
import_limit=0,
|
699 |
export_limit=0,
|
700 |
flags=0)
|
701 |
|
702 |
rdef_user_max_quota = self.define_attribute_of_resource(
|
703 |
abs_resource_name=abs_resource_name, |
704 |
attribute_name='user_max',
|
705 |
quantity=0,
|
706 |
capacity=user_max_quota, |
707 |
import_limit=0,
|
708 |
export_limit=0,
|
709 |
flags=0)
|
710 |
|
711 |
rdef_group_max_quota = self.define_attribute_of_resource(
|
712 |
abs_resource_name=abs_resource_name, |
713 |
attribute_name='group_max',
|
714 |
quantity=0,
|
715 |
capacity=group_max_quota, |
716 |
import_limit=0,
|
717 |
export_limit=0,
|
718 |
flags=0)
|
719 |
|
720 |
|
721 |
return {'resource_quota': resource_quota, |
722 |
'user_quota': rdef_user_quota,
|
723 |
'user_max_quota': rdef_user_max_quota,
|
724 |
'group_max_quota': rdef_group_max_quota
|
725 |
} |
726 |
|
727 |
|
728 |
def define_attribute_of_resource(self, |
729 |
abs_resource_name, |
730 |
attribute_name, |
731 |
quantity, |
732 |
capacity, |
733 |
import_limit, |
734 |
export_limit, |
735 |
flags): |
736 |
"""
|
737 |
Defines an ``int`` attribute for global resource named
|
738 |
``global_resource_name``.
|
739 |
|
740 |
The ``attribute_name`` must be simple, that is not an absolute name.
|
741 |
|
742 |
Returns the computed name of the created attribute. Note that the
|
743 |
computed name is not the same as ``attribute_name``.
|
744 |
|
745 |
The implementation maps the attribute to a Quota Holder resource under
|
746 |
the node (Quota Holder entity) named ``global_resource_name``. The
|
747 |
respective value is defined via Quota Holder quotas.
|
748 |
"""
|
749 |
return self.__create_attribute_of_node_for_resource( |
750 |
node_name=abs_resource_name, |
751 |
node_label='abs_resource_name',
|
752 |
resource_name=abs_resource_name, |
753 |
# `rdef` means we define for a Resource
|
754 |
# The attribute name goes next
|
755 |
# The absolute resource name goes next
|
756 |
# (will be added by the called method)
|
757 |
attribute_name=attribute_name, |
758 |
quantity=quantity, |
759 |
capacity=capacity, |
760 |
import_limit=import_limit, |
761 |
export_limit=export_limit, |
762 |
flags=flags) |
763 |
|
764 |
|
765 |
|
766 |
def define_group(self, group_name): |
767 |
"""
|
768 |
Creates a new group under 'system/groups'.
|
769 |
|
770 |
The ``group_name`` can either be an absolute name
|
771 |
(e.g. 'system/groups/mygroup') or a relative name (e.g. 'mygroup').
|
772 |
|
773 |
Returns the absolute group name of the created group. Note that this
|
774 |
may be different from ``group_name``.
|
775 |
|
776 |
You must always use the returned ``group_name`` as the
|
777 |
most authoritative value instead of the one passed as a parameter.
|
778 |
|
779 |
No further resource assignment is done, you must use
|
780 |
``define_resource_of_group``.
|
781 |
|
782 |
The implementation maps a group to a Quota Holder entity.
|
783 |
"""
|
784 |
self.ensure_groups_node()
|
785 |
# get name in absolute form
|
786 |
abs_group_name = make_abs_group_name(group_name) |
787 |
|
788 |
# Create hierarchy on demand
|
789 |
self.create_node(abs_group_name, 'abs_group_name') |
790 |
return abs_group_name
|
791 |
|
792 |
|
793 |
|
794 |
def define_attribute_of_group_for_resource(self, |
795 |
abs_group_name, |
796 |
abs_resource_name, |
797 |
attribute_name, |
798 |
quantity, |
799 |
capacity, |
800 |
import_limit, |
801 |
export_limit, |
802 |
flags): |
803 |
check_abs_group_name(abs_group_name) |
804 |
check_abs_resource_name(abs_resource_name) |
805 |
|
806 |
return self.__create_attribute_of_node_for_resource( |
807 |
node_name=abs_group_name, |
808 |
node_label='abs_group_name',
|
809 |
resource_name=abs_resource_name, |
810 |
attribute_name=attribute_name, |
811 |
quantity=quantity, |
812 |
capacity=capacity, |
813 |
import_limit=import_limit, |
814 |
export_limit=export_limit, |
815 |
flags=flags) |
816 |
|
817 |
|
818 |
def define_resource_of_group(self, |
819 |
group_name, |
820 |
resource_name, |
821 |
# How much the group gets in total
|
822 |
resource_amount, |
823 |
# How much the group gives to the user
|
824 |
user_quota, |
825 |
resource_amount_is_ondemand = False):
|
826 |
"""
|
827 |
Defines a resource that a group provides to its users.
|
828 |
"""
|
829 |
abs_group_name = make_abs_group_name(group_name) |
830 |
abs_resource_name = make_abs_resource_name(resource_name) |
831 |
|
832 |
# Not implemented yet
|
833 |
if resource_amount_is_ondemand:
|
834 |
raise Exception( |
835 |
"On demand group capacity is not supported for group %s" % (
|
836 |
group_name)) |
837 |
|
838 |
if not self.resource_exists(abs_resource_name): |
839 |
raise Exception( |
840 |
"Cannot define resource '%s' for group '%s' because the global resource '%s' does not exist" %(
|
841 |
resource_name, |
842 |
group_name, |
843 |
abs_resource_name)) |
844 |
|
845 |
if not self.node_exists(abs_group_name): |
846 |
raise Exception( |
847 |
"Cannot define resource '%s' for group '%s' because the group does not exist" %(
|
848 |
resource_name, |
849 |
group_name)) |
850 |
|
851 |
# Define the resource quotas for the group (initially empty)
|
852 |
group_resource_quota = self.set_quota(
|
853 |
entity=abs_group_name, |
854 |
resource=abs_resource_name, |
855 |
quantity=0,
|
856 |
capacity=resource_amount, |
857 |
import_limit=None,
|
858 |
export_limit=None,
|
859 |
flags=0)
|
860 |
# ... and do the quota transfer from the resource node
|
861 |
self.issue_one_commission(
|
862 |
target_entity=abs_group_name, |
863 |
source_entity=abs_resource_name, |
864 |
resource=abs_resource_name, |
865 |
quantity=resource_amount) |
866 |
|
867 |
# Other definitions
|
868 |
user_quota = self.define_attribute_of_group_for_resource(
|
869 |
abs_group_name=abs_group_name, |
870 |
abs_resource_name=abs_resource_name, |
871 |
attribute_name='user',
|
872 |
quantity=0,
|
873 |
capacity=user_quota, |
874 |
import_limit=0,
|
875 |
export_limit=0,
|
876 |
flags=0)
|
877 |
|
878 |
return {'group_resource_quota': group_resource_quota, |
879 |
'user_quota': user_quota}
|
880 |
|
881 |
|
882 |
#+++##########################################
|
883 |
# Public, high-level API.
|
884 |
#+++##########################################
|
885 |
# Public, high-level API.
|