Statistics
| Branch: | Tag: | Revision:

root / commissioning / hlapi / api.py @ 65769612

History | View | Annotate | Download (19.2 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 method_accepts
7
from util import accepts
8
from util import returns
9
from util import NameOfSystemNode
10
from util import NameOfResourcesNode
11
from util import NameOfGroupsNode
12
from util import NameOfUsersNode
13
from util import make_abs_global_resource_name
14
from util import make_abs_group_name
15
from util import make_abs_user_name
16
from util import reparent_child_name_under 
17
from util import check_abs_global_resource_name
18
from util import parent_abs_name_of
19
from util import relative_child_name_under
20
from util import make_rel_global_resource_name
21
from util import make_rel_group_name
22
from util import make_rel_user_name
23
from util import check_name
24
from util import level_of_node
25
from util import check_abs_name
26

    
27
class HighLevelAPI(object):
28
    """
29
    High-level Quota Holder API that supports definitions of resources,
30
    groups and users and the transfer of resource quotas from
31
    respective resources to both groups and users.
32
    
33
    Internally it uses the low-level Quota Holder API, so it maps everything
34
    into Entities, Resources, Policies and Limits.
35
    
36
    High-level API Terminology
37
    
38
    A node is just an entity with an absolute name. The root node called
39
    ``system`` is always present and is enforced by the low-level API.
40
    
41
    The meaning of a few default nodes, called the default parent nodes,
42
    is predefined.
43
    ``system/resources`` is the parent node of all resources known to ~okeanos.
44
    ``system/groups`` is the parent node of all user groups known to ~okeanos.
45
    ``system/users`` is the parent node of all users known to ~okeanos.
46
        
47
    A resource is always a node under ``system/resources``.
48
    A group is always a node under ``system/groups``.
49
    A user is always a node under ``system/users``.
50
    
51
    """
52

    
53
    def __init__(self, qh = None, **kwd):
54
        if qh is None:
55
            def new_http_qh(url):
56
                from commissioning.clients.http import HTTP_API_Client
57
                from commissioning import QuotaholderAPI
58
                class QuotaholderHTTP(HTTP_API_Client):
59
                    api_spec = QuotaholderAPI()
60
                return QuotaholderHTTP(url)
61
            
62
            def check_dict(d):
63
                if d.has_key('QH_URL'):
64
                    return kwd['QH_URL']
65
                elif d.has_key('QH_HOST') and d.has_key('QH_PORT'):
66
                    return "http://%s:%s/api/quotaholder/v" % (
67
                        d['QH_HOST'],
68
                        d['QH_PORT'])
69
                else:
70
                    return None
71
            
72
            from os import environ
73
            url = check_dict(kwd) or check_dict(environ)
74
            if url is None:
75
                raise Exception("No quota holder low-level api specified")
76
            del environ
77
            self.__qh = new_http_qh(url)
78
            
79
        else:
80
            self.__qh = qh
81
            
82
        self.__context = check_context(kwd.get('context') or {})
83
        self.__node_keys = {
84
            NameOfSystemNode: check_string('system_key',
85
                                           kwd.get('system_key') or ''),
86
            NameOfResourcesNode: check_string('resources_key',
87
                                              kwd.get('resources_key') or ''),
88
            NameOfGroupsNode: check_string('groups_key',
89
                                           kwd.get('groups_key') or '')
90
        }
91

    
92

    
93
    # internal API
94
    @method_accepts(str, str, str, str,
95
                    int, int, int, int, int)
96
    @returns(str)
97
    def __create_attribute_of_node_for_resource(self,
98
                                                abs_or_not_node_name,
99
                                                intended_parent_node_name,
100
                                                resource_name,
101
                                                simple_attribute_name,
102
                                                quantity,
103
                                                capacity,
104
                                                import_limit,
105
                                                export_limit,
106
                                                flags):
107
        #
108
        check_abs_name(intended_parent_node_name,
109
                        'intended_parent_node_name')
110
        
111
        intended_parent_node_name = self.ensure_node(intended_parent_node_name)
112
        
113
        # Fix node name to its intended absolute form just in case
114
        node_name = reparent_child_name_under(child_name=abs_or_not_node_name,
115
                                              parent_node_name=intended_parent_node_name,
116
                                              child_label='abs_or_not_node_name',
117
                                              parent_label='intended_parent_node_name')
118

    
119
        self.ensure_resources_node()
120
        
121
        abs_resource_name = make_abs_global_resource_name(resource_name)
122
        self.ensure_node(abs_resource_name)
123
        
124
        # Fix resource name to its relative form just in case
125
        relative_resource_name = make_rel_global_resource_name(resource_name,
126
                                                                  'resource_name')
127
        
128
        computed_attribute_name = '%s_%s' % (simple_attribute_name,
129
                                             relative_resource_name)
130
        
131
        rejected  = self.__qh.set_quota(
132
            context=self.__context,
133
            set_quota=[(
134
                node_name,               # Node -> QH entity
135
                computed_attribute_name, # Attribute -> QH resource
136
                self.get_cached_node_key(node_name),
137
                quantity,
138
                capacity, 
139
                import_limit, 
140
                export_limit, 
141
                flags
142
            )]
143
        )
144
        
145
        if len(rejected) > 0:
146
            raise Exception(
147
                "Could not create attribute '%s' [='%s'] for node '%s' [='%s'], related to resource '%s' [='%s']" % (
148
                    simple_attribute_name,
149
                    computed_attribute_name,
150
                    abs_or_not_node_name,
151
                    node_name,
152
                    resource_name,
153
                    abs_resource_name
154
            ))
155
            
156
        return computed_attribute_name
157

    
158

    
159
    @method_accepts(str)
160
    @returns(str)
161
    def get_cached_node_key(self, node_name):
162
        check_abs_name(node_name, 'node_name')
163
        return self.__node_keys.get(node_name) or '' # sane default
164

    
165

    
166
    @method_accepts(str, str)
167
    @returns(type(None))
168
    def set_cached_node_key(self, abs_node_name, node_key, label='abs_node_name'):
169
        check_abs_name(abs_node_name, label)
170
        check_node_key(node_key)
171
        if node_key is None:
172
            node_key = ''
173
        self.__node_keys[abs_node_name] = node_key
174

    
175

    
176
    @returns(dict)
177
    def node_keys(self):
178
        return self.__node_keys.copy() # Client cannot mess with the original
179
    
180
    
181
    @method_accepts(str)
182
    @returns(list)
183
    def get_node_children(self, node_name):
184
        check_abs_name(node_name, 'node_name')
185
        entities = self.__qh.list_entities(context=self.__context,
186
                                entity=node_name,
187
                                key=self.get_cached_node_key(node_name))
188
        return entities
189
    
190
    
191
    def get_toplevel_nodes(self):
192
        """
193
        Return all nodes with absolute name starting with 'system/'
194
        """
195
        all_implied_system_children = self.get_node_children(NameOfSystemNode)
196
        return [node_name for node_name in all_implied_system_children \
197
                if node_name.startswith('system/') and \
198
                level_of_node(node_name) == 1]
199
    
200
        
201
    def get_global_resources(self):
202
        self.ensure_resources_node()
203
        return self.get_node_children(NameOfResourcesNode)
204
    
205
    
206
    def get_groups(self):
207
        self.ensure_groups_node()
208
        return self.get_node_children(NameOfGroupsNode)
209
    
210
    
211
    def get_users(self):
212
        self.ensure_users_node()
213
        return self.get_node_children(NameOfUsersNode)
214
    
215
    
216
    @method_accepts(str, str)
217
    @returns(str)
218
    def ensure_node(self, abs_node_name, label='abs_node_name'):
219
        if not self.has_node(abs_node_name, label):
220
            return self.create_node(abs_node_name, label)
221
        else:
222
            return abs_node_name
223

    
224
    
225
    @returns(str)
226
    def ensure_resources_node(self):
227
        """
228
        Ensure that the node 'system/resources' exists.
229
        """
230
        return self.ensure_node(NameOfResourcesNode, 'NameOfResourcesNode')
231

    
232

    
233
    @returns(str)
234
    def ensure_groups_node(self):
235
        """
236
        Ensure that the node 'system/groups' exists.
237
        """
238
        return self.ensure_node(NameOfGroupsNode, 'NameOfGroupsNode')
239

    
240

    
241
    @returns(str)
242
    def ensure_users_node(self):
243
        """
244
        Ensure that the node 'system/users' exists.
245
        """
246
        return self.ensure_node(NameOfUsersNode, 'NameOfGroupsNode')
247

    
248

    
249
    @method_accepts(str, str)
250
    @returns(bool)
251
    def has_node(self, abs_node_name, label='abs_node_name'):
252
        """
253
        Checks if an entity with the absolute name ``abs_node_name`` exists.
254
        
255
        Returns ``True``/``False`` accordingly.
256
        """
257
        check_abs_name(abs_node_name, label)
258
        node_key = self.get_cached_node_key(abs_node_name)
259
        entity_owner_list = self.__qh.get_entity(
260
            context=self.__context,
261
            get_entity=[(abs_node_name, node_key)]
262
        )
263
        return len(entity_owner_list) == 1 # TODO: any other check here?
264

    
265

    
266
    @method_accepts(str, str)
267
    @returns(str)
268
    def create_node(self, node_name, label='node_name'):
269
        """
270
        Creates a node with an absolute name ``node_name``.
271
        
272
        If the hierarchy up to ``node_name`` does not exist, then it is
273
        created on demand.
274
        
275
        Returns the absolute ``node_name``.
276
        
277
        The implementation maps a node to a Quota Holder entity.
278
        """
279
        check_abs_name(node_name, label)
280
        
281
        if is_system_node(node_name):
282
            # ``system`` entity always exists
283
            return node_name
284

    
285
        parent_node_name = parent_abs_name_of(node_name, label)
286
        # Recursively create hierarchy. Beware the keys must be known.
287
        self.ensure_node(parent_node_name, label)
288

    
289
        node_key = self.get_cached_node_key(node_name)
290
        parent_node_key = self.get_cached_node_key(parent_node_name)
291

    
292
        rejected = self.__qh.create_entity(
293
            context=self.__context,
294
            create_entity=[
295
                (
296
                    node_name,
297
                    parent_node_name,
298
                    node_key,
299
                    parent_node_key
300
                    )
301
            ]
302
        )
303
        if len(rejected) > 0:
304
            raise Exception("Could not create node '%s'" % (node_name,))
305
        else:
306
            return node_name
307

    
308

    
309
    @method_accepts(str)
310
    @returns(bool)
311
    def has_global_resource(self, abs_resource_name):
312
        check_abs_global_resource_name(abs_resource_name)
313
        return self.has_node(abs_resource_name)
314

    
315

    
316
    @method_accepts(str)
317
    def define_global_resource(self, resource_name):
318
        """
319
        Defines a resource globally known to Quota Holder.
320
        
321
        Returns the absolute name of the created resource. Note that this may
322
        be different from ``resource_name``.
323
        
324
        The implementation maps a global resource to a Quota Holder entity
325
        (so this is equivalent to a node in the high-level API but an extra
326
        check is made to ensure the resource is under 'system/resources').
327
        """
328
        
329
        # If the name is not in absolute form, make it so.
330
        # E.g. if it is 'pithos+' make it 'system/resources/pithos+'
331
        abs_resource_name = make_abs_global_resource_name(resource_name)
332
        
333
        # Create hierarchy on demand
334
        return self.create_node(abs_resource_name, 'abs_resource_name')
335
        
336
    
337
    @method_accepts(str, str, int, int, int, int, int)
338
    @returns(str)
339
    def define_attribute_of_global_resource(self,
340
                                            global_resource_name,
341
                                            attribute_name,
342
                                            quantity,
343
                                            capacity,
344
                                            import_limit,
345
                                            export_limit,
346
                                            flags):
347
        """
348
        Defines an ``int`` attribute for global resource named
349
        ``global_resource_name``.
350
        
351
        The ``attribute_name`` must be simple, that is not an absolute name.
352
        
353
        Returns the computed name of the created attribute. Note that the
354
        computed name is not the same as ``attribute_name``.
355
        
356
        The implementation maps the attribute to a Quota Holder resource under
357
        the node (Quota Holder entity) named ``global_resource_name``. The
358
        respective value is defined via Quota Holder quotas. 
359
        """
360
        
361
        return self.__create_attribute_of_node_for_resource(
362
            abs_or_not_node_name=global_resource_name,
363
            intended_parent_node_name=NameOfResourcesNode,
364
            resource_name=global_resource_name,
365
            # r means we define for Resource
366
            # The attribute name goes next
367
            # The relative resource name goes next
368
            #    (will be added by the called method)
369
            simple_attribute_name='r_%s' % (attribute_name),
370
            quantity=quantity,
371
            capacity=capacity,
372
            import_limit=import_limit,
373
            export_limit=export_limit,
374
            flags=flags)
375

    
376
            
377
    
378
    @method_accepts(str, str)
379
    @returns(str)
380
    def define_group(self, group_name, group_node_key=''):
381
        """
382
        Creates a new group under 'system/groups'.
383

384
        The ``group_name`` can either be an absolute name
385
        (e.g. 'system/groups/mygroup') or a relative name (e.g. 'mygroup').
386
        
387
        Returns the absolute group name of the created group. Note that this
388
        may be different from ``group_name``. 
389
        
390
        You must always use the returned ``group_name`` as the
391
        most authoritative value instead of the one passed as a parameter.
392

393
        No further resource assignment is done, you must use
394
        ``define_group_resource``.
395

396
        The implementation maps a group to a Quota Holder entity.
397
        """
398
        check_node_key(group_node_key)
399

    
400
        self.ensure_groups_node()
401
        # get name in absolute form
402
        abs_group_name = make_abs_group_name(group_name)
403

    
404
        # Create hierarchy on demand
405
        self.create_node(abs_group_name, 'abs_group_name')
406
        self.set_cached_node_key(abs_group_name, group_node_key)
407
        return abs_group_name
408
    
409

    
410
    
411
    @method_accepts(str, str, str, int, int, int, int, int)
412
    @returns(str)
413
    def define_attribute_of_group_for_resource(self,
414
                                               group_name,
415
                                               attribute_name,
416
                                               resource_name,
417
                                               quantity,
418
                                               capacity,
419
                                               import_limit,
420
                                               export_limit,
421
                                               flags):
422
        
423
        return self.__create_attribute_of_node_for_resource(
424
            abs_or_not_node_name=group_name,
425
            intended_parent_node_name=NameOfGroupsNode,
426
            resource_name=resource_name,
427
            # g means we define for G
428
            # The attribute name goes next
429
            # The relative resource name goes next
430
            #    (will be added by the called method)
431
            simple_attribute_name='g_%s' % (attribute_name),
432
            quantity=quantity,
433
            capacity=capacity,
434
            import_limit=import_limit,
435
            export_limit=export_limit,
436
            flags=flags)
437

    
438

    
439
    @method_accepts(str, str, int, int, int, int, int, int, int)
440
    @returns(type(None))
441
    def define_group_resource(self,
442
                              group_name,
443
                              resource_name,
444
                              limit_per_group,
445
                              limit_per_user,
446
                              operational_quantity,
447
                              operational_capacity,
448
                              operational_import_limit,
449
                              operational_export_limit,
450
                              operational_flags):
451
        """
452
        Defines a resource that a group provides to its users.
453
        """
454
        abs_group_name = make_abs_group_name(group_name)
455
        abs_resource_name = make_abs_global_resource_name(resource_name)
456
        
457
        if not self.has_global_resource(abs_resource_name):
458
            raise Exception(
459
                "Cannot define resource '%s' for group '%s' because the global resource '%s' does not exist" %(
460
                    resource_name,
461
                    group_name,
462
                    abs_resource_name))
463
            
464
        if not self.has_node(abs_group_name):
465
            raise Exception(
466
                "Cannot define resource '%s' for group '%s' because the group does not exist" %(
467
                    resource_name,
468
                    group_name))
469

    
470
        if limit_per_user >= 0:
471
            self.define_attribute_of_group_for_resource(group_name=abs_group_name,
472
                                                        attribute_name='def_peruser',
473
                                                        resource_name=abs_resource_name,
474
                                                        quantity=0,
475
                                                        capacity=limit_per_user,
476
                                                        import_limit=0,
477
                                                        export_limit=0,
478
                                                        flags=0)
479
        else:
480
            # The limit will always be obtained from the global resource
481
            pass
482

    
483
        self.define_attribute_of_group_for_resource(group_name=abs_group_name,
484
                                                    attribute_name='def_pergroup',
485
                                                    resource_name=abs_resource_name,
486
                                                    quantity=0,
487
                                                    capacity=limit_per_group,
488
                                                    import_limit=0,
489
                                                    export_limit=0,
490
                                                    flags=0)
491

    
492
        self.define_attribute_of_group_for_resource(group_name=abs_group_name,
493
                                                    attribute_name='operational',
494
                                                    resource_name=abs_resource_name,
495
                                                    quantity=operational_quantity,
496
                                                    capacity=operational_capacity,
497
                                                    import_limit=operational_import_limit,
498
                                                    export_limit=operational_export_limit,
499
                                                    flags=operational_flags)
500