Statistics
| Branch: | Tag: | Revision:

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.