Commit cf8a17c0 authored by Christos KK Loverdos's avatar Christos KK Loverdos
Browse files

WIP high-level API. Readjusted how resources are attached to groups

parent d46ae215
...@@ -3,9 +3,6 @@ from util import check_node_key ...@@ -3,9 +3,6 @@ from util import check_node_key
from util import is_system_node from util import is_system_node
from util import check_context from util import check_context
from util import check_string from util import check_string
from util import method_accepts
from util import accepts
from util import returns
from util import NameOfSystemNode from util import NameOfSystemNode
from util import NameOfResourcesNode from util import NameOfResourcesNode
from util import NameOfGroupsNode from util import NameOfGroupsNode
...@@ -152,6 +149,8 @@ class HighLevelAPI(object): ...@@ -152,6 +149,8 @@ class HighLevelAPI(object):
""" """
def __init__(self, qh = None, **kwd): def __init__(self, qh = None, **kwd):
self.__client_key = kwd.get('client_key') or ''
if qh is None: if qh is None:
def new_http_qh(url): def new_http_qh(url):
from commissioning.clients.http import HTTP_API_Client from commissioning.clients.http import HTTP_API_Client
...@@ -194,9 +193,6 @@ class HighLevelAPI(object): ...@@ -194,9 +193,6 @@ class HighLevelAPI(object):
# internal API # internal API
@method_accepts(str, str, str, str,
int, int, int, int, int)
@returns(str)
def __create_attribute_of_node_for_resource(self, def __create_attribute_of_node_for_resource(self,
abs_or_not_node_name, abs_or_not_node_name,
intended_parent_node_name, intended_parent_node_name,
...@@ -342,38 +338,80 @@ class HighLevelAPI(object): ...@@ -342,38 +338,80 @@ class HighLevelAPI(object):
if len(rejected) > 0: if len(rejected) > 0:
raise Exception("Could not release entity '%s'" % (entity)) raise Exception("Could not release entity '%s'" % (entity))
def qh_make_one_commission(self, target_entity, target_entity_key, def qh_issue_one_commission(self, target_entity, target_entity_key,
client_key, owner, owner_key, owner, owner_key,
source_entity, source_resource, quantity): source_entity, resource, quantity):
tx_id = self.__qh.issue_commission(context=self.__context, client_key = self.__client_key
target=target_entity, tx_id = self.__qh.issue_commission(
key=target_entity_key, context=self.__context,
clientkey=client_key, target=target_entity,
owner=owner, key=target_entity_key,
ownerkey=owner_key, clientkey=client_key,
provisions=[(source_entity, owner=owner,
source_resource, ownerkey=owner_key,
quantity)]) provisions=[(source_entity,
self.__qh.accept_commission(context=self.__context, resource,
clientkey=client_key, quantity)])
serials=[tx_id]) try:
return tx_id self.__qh.accept_commission(
context=self.__context,
clientkey=client_key,
serials=[tx_id])
return tx_id
except:
self.__qh.reject_commission(
context=self.__context,
clientkey=client_key,
serials=[tx_id])
#---########################################## #---##########################################
# Public, low-level API. # Public, low-level API.
# We expose some low-level Quota Holder API # We expose some low-level Quota Holder API
#---########################################## #---##########################################
#+++##########################################
# Public, high-level API.
#+++##########################################
def get_quota(self, entity, resource):
return self.qh_get_quota(entity,
resource,
self.get_cached_node_key(entity))
def set_quota(self, entity, resource,
quantity, capacity,
import_limit, export_limit, flags):
return self.qh_set_quota(entity=entity,
resource=resource,
key=self.get_cached_node_key(entity),
quantity=quantity,
capacity=capacity,
import_limit=import_limit,
export_limit=export_limit,
flags=flags)
def issue_one_commission(self, target_entity, source_entity,
resource, quantity):
check_abs_name(target_entity, 'target_entity')
check_abs_name(source_entity, 'source_entity')
return self.qh_issue_one_commission(
target_entity=target_entity,
target_entity_key=self.get_cached_node_key(target_entity),
owner='', # We ignore the owner. Everything must exist
owner_key='',
source_entity=source_entity,
resource=resource,
quantity=quantity)
@method_accepts(str)
@returns(str)
def get_cached_node_key(self, node_name): def get_cached_node_key(self, node_name):
check_abs_name(node_name, 'node_name') check_abs_name(node_name, 'node_name')
return self.__node_keys.get(node_name) or '' # sane default return self.__node_keys.get(node_name) or '' # sane default
@method_accepts(str, str)
@returns(type(None))
def set_cached_node_key(self, abs_node_name, node_key, label='abs_node_name'): def set_cached_node_key(self, abs_node_name, node_key, label='abs_node_name'):
check_abs_name(abs_node_name, label) check_abs_name(abs_node_name, label)
check_node_key(node_key) check_node_key(node_key)
...@@ -382,13 +420,10 @@ class HighLevelAPI(object): ...@@ -382,13 +420,10 @@ class HighLevelAPI(object):
self.__node_keys[abs_node_name] = node_key self.__node_keys[abs_node_name] = node_key
@returns(dict)
def node_keys(self): def node_keys(self):
return self.__node_keys.copy() # Client cannot mess with the original return self.__node_keys.copy() # Client cannot mess with the original
@method_accepts(str)
@returns(list)
def get_node_children(self, node_name): def get_node_children(self, node_name):
check_abs_name(node_name, 'node_name') check_abs_name(node_name, 'node_name')
all_entities = self.__qh.list_entities( all_entities = self.__qh.list_entities(
...@@ -420,17 +455,20 @@ class HighLevelAPI(object): ...@@ -420,17 +455,20 @@ class HighLevelAPI(object):
self.ensure_users_node() self.ensure_users_node()
return self.get_node_children(NameOfUsersNode) return self.get_node_children(NameOfUsersNode)
@method_accepts(str, str)
@returns(str)
def ensure_node(self, abs_node_name, label='abs_node_name'): def ensure_node(self, abs_node_name, label='abs_node_name'):
if not self.has_node(abs_node_name, label): if not self.has_node(abs_node_name, label):
return self.create_node(abs_node_name, label) return self.create_node(abs_node_name, label)
else: else:
return abs_node_name return abs_node_name
def ensure_top_level_nodes(self):
return [self.ensure_resources_node(),
self.ensure_users_node(),
self.ensure_groups_node()]
@returns(str)
def ensure_resources_node(self): def ensure_resources_node(self):
""" """
Ensure that the node 'system/resources' exists. Ensure that the node 'system/resources' exists.
...@@ -438,7 +476,6 @@ class HighLevelAPI(object): ...@@ -438,7 +476,6 @@ class HighLevelAPI(object):
return self.ensure_node(NameOfResourcesNode, 'NameOfResourcesNode') return self.ensure_node(NameOfResourcesNode, 'NameOfResourcesNode')
@returns(str)
def ensure_groups_node(self): def ensure_groups_node(self):
""" """
Ensure that the node 'system/groups' exists. Ensure that the node 'system/groups' exists.
...@@ -446,7 +483,6 @@ class HighLevelAPI(object): ...@@ -446,7 +483,6 @@ class HighLevelAPI(object):
return self.ensure_node(NameOfGroupsNode, 'NameOfGroupsNode') return self.ensure_node(NameOfGroupsNode, 'NameOfGroupsNode')
@returns(str)
def ensure_users_node(self): def ensure_users_node(self):
""" """
Ensure that the node 'system/users' exists. Ensure that the node 'system/users' exists.
...@@ -454,8 +490,6 @@ class HighLevelAPI(object): ...@@ -454,8 +490,6 @@ class HighLevelAPI(object):
return self.ensure_node(NameOfUsersNode, 'NameOfGroupsNode') return self.ensure_node(NameOfUsersNode, 'NameOfGroupsNode')
@method_accepts(str, str)
@returns(bool)
def has_node(self, abs_node_name, label='abs_node_name'): def has_node(self, abs_node_name, label='abs_node_name'):
""" """
Checks if an entity with the absolute name ``abs_node_name`` exists. Checks if an entity with the absolute name ``abs_node_name`` exists.
...@@ -471,9 +505,22 @@ class HighLevelAPI(object): ...@@ -471,9 +505,22 @@ class HighLevelAPI(object):
return len(entity_owner_list) == 1 # TODO: any other check here? return len(entity_owner_list) == 1 # TODO: any other check here?
@method_accepts(str, str) def has_group(self, group_name):
@returns(str) abs_group_name = make_abs_group_name(group_name)
def create_node(self, node_name, label='node_name'): return self.has_node(abs_group_name, 'abs_group_name')
def has_global_resource(self, resource_name):
abs_resource_name = make_abs_global_resource_name(resource_name)
return self.has_node(abs_resource_name, 'abs_resource_name')
def has_user(self, user_name):
abs_user_name = make_abs_user_name(user_name)
return self.has_node(abs_user_name, 'abs_user_name')
def create_node(self, node_name, node_label):
""" """
Creates a node with an absolute name ``node_name``. Creates a node with an absolute name ``node_name``.
...@@ -484,15 +531,15 @@ class HighLevelAPI(object): ...@@ -484,15 +531,15 @@ class HighLevelAPI(object):
The implementation maps a node to a Quota Holder entity. The implementation maps a node to a Quota Holder entity.
""" """
check_abs_name(node_name, label) check_abs_name(node_name, node_label)
if is_system_node(node_name): if is_system_node(node_name):
# ``system`` entity always exists # ``system`` entity always exists
return node_name return node_name
parent_node_name = parent_abs_name_of(node_name, label) parent_node_name = parent_abs_name_of(node_name, node_label)
# Recursively create hierarchy. Beware the keys must be known. # Recursively create hierarchy. Beware the keys must be known.
self.ensure_node(parent_node_name, label) self.ensure_node(parent_node_name, node_label)
node_key = self.get_cached_node_key(node_name) node_key = self.get_cached_node_key(node_name)
parent_node_key = self.get_cached_node_key(parent_node_name) parent_node_key = self.get_cached_node_key(parent_node_name)
...@@ -509,19 +556,11 @@ class HighLevelAPI(object): ...@@ -509,19 +556,11 @@ class HighLevelAPI(object):
] ]
) )
if len(rejected) > 0: if len(rejected) > 0:
raise Exception("Could not create node '%s'" % (node_name,)) raise Exception("Could not create %s='%s'. Maybe it already exists?" % (node_label, node_name,))
else: else:
return node_name return node_name
@method_accepts(str)
@returns(bool)
def has_global_resource(self, abs_resource_name):
check_abs_global_resource_name(abs_resource_name)
return self.has_node(abs_resource_name)
@method_accepts(str)
def define_global_resource(self, resource_name): def define_global_resource(self, resource_name):
""" """
Defines a resource globally known to Quota Holder. Defines a resource globally known to Quota Holder.
...@@ -542,8 +581,6 @@ class HighLevelAPI(object): ...@@ -542,8 +581,6 @@ class HighLevelAPI(object):
return self.create_node(abs_resource_name, 'abs_resource_name') return self.create_node(abs_resource_name, 'abs_resource_name')
@method_accepts(str, str, int, int, int, int, int)
@returns(str)
def define_attribute_of_global_resource(self, def define_attribute_of_global_resource(self,
global_resource_name, global_resource_name,
attribute_name, attribute_name,
...@@ -583,8 +620,6 @@ class HighLevelAPI(object): ...@@ -583,8 +620,6 @@ class HighLevelAPI(object):
@method_accepts(str, str)
@returns(str)
def define_group(self, group_name, group_node_key=''): def define_group(self, group_name, group_node_key=''):
""" """
Creates a new group under 'system/groups'. Creates a new group under 'system/groups'.
...@@ -616,8 +651,6 @@ class HighLevelAPI(object): ...@@ -616,8 +651,6 @@ class HighLevelAPI(object):
@method_accepts(str, str, str, int, int, int, int, int)
@returns(str)
def define_attribute_of_group_for_resource(self, def define_attribute_of_group_for_resource(self,
group_name, group_name,
attribute_name, attribute_name,
...@@ -644,13 +677,14 @@ class HighLevelAPI(object): ...@@ -644,13 +677,14 @@ class HighLevelAPI(object):
flags=flags) flags=flags)
@method_accepts(str, str, int, int, int, int, int, int, int)
@returns(type(None))
def define_group_resource(self, def define_group_resource(self,
group_name, group_name,
resource_name, resource_name,
limit_per_group, group_quota,
limit_per_user, group_import_limit,
group_export_limit,
group_flags,
per_user_quota,
operational_quantity, operational_quantity,
operational_capacity, operational_capacity,
operational_import_limit, operational_import_limit,
...@@ -675,34 +709,28 @@ class HighLevelAPI(object): ...@@ -675,34 +709,28 @@ class HighLevelAPI(object):
resource_name, resource_name,
group_name)) group_name))
if limit_per_user >= 0: # Define the resource quotas for the group (initially empty)
self.define_attribute_of_group_for_resource(group_name=abs_group_name, # and do the quota transfer from the global resource
attribute_name='def_peruser', group_resource_quota = self.set_quota(
resource_name=abs_resource_name, entity=abs_group_name,
quantity=0, resource=abs_resource_name,
capacity=limit_per_user, quantity=0,
import_limit=0, capacity=0,
export_limit=0, import_limit=group_import_limit,
flags=0) export_limit=group_export_limit,
else: flags=group_flags)
# The limit will always be obtained from the global resource
pass
self.define_attribute_of_group_for_resource(group_name=abs_group_name,
attribute_name='def_pergroup',
resource_name=abs_resource_name,
quantity=0,
capacity=limit_per_group,
import_limit=0,
export_limit=0,
flags=0)
self.define_attribute_of_group_for_resource(group_name=abs_group_name,
attribute_name='operational',
resource_name=abs_resource_name,
quantity=operational_quantity,
capacity=operational_capacity,
import_limit=operational_import_limit,
export_limit=operational_export_limit,
flags=operational_flags)
self.issue_one_commission(
target_entity=abs_group_name,
source_entity=abs_resource_name,
resource=abs_resource_name,
quantity=group_quota)
return self.get_quota(entity=abs_group_name,
resource=abs_resource_name)
#+++##########################################
# Public, high-level API.
#+++##########################################
# Public, high-level API.
...@@ -3,127 +3,6 @@ import sys ...@@ -3,127 +3,6 @@ import sys
def isstr(s): def isstr(s):
return issubclass(type(s), basestring) return issubclass(type(s), basestring)
def compatible_type(given_t, expected_t):
if issubclass(expected_t, basestring):
expected_t = basestring
if issubclass(given_t, basestring):
given_t = basestring
return given_t == expected_t
def compatible_input_types(given, expected):
# print "given=%s, expected=%s" % (given, expected)
if len(given) != len(expected):
return False
for i in xrange(len(given)):
if not compatible_type(given[i], expected[i]):
return False
return True
def compatible_return_type(given, expected):
return compatible_type(given, expected)
def method_accepts(*types):
'''Method decorator. Checks decorated function's arguments are
of the expected types. The self argument is ignored. This is
based on the ``accepts`` decorator.
Parameters:
types -- The expected types of the inputs to the decorated function.
Must specify type for each parameter.
'''
try:
def decorator(f):
def newf(*args):
args_to_check = args[1:] # Throw away self (or cls)
if len(args_to_check) != len(types):
raise TypeError("Wrong number of arguments. Expected is %s (of types %s), given is %s" % (len(types), types, len(args_to_check)))
argtypes = tuple(map(type, args_to_check))
# if argtypes != types:
if not compatible_input_types(argtypes, types):
msg = info(f.__name__, types, argtypes, 0)
raise TypeError, msg
return f(*args)
newf.__name__ = f.__name__
return newf
return decorator
except KeyError, key:
raise KeyError, key + "is not a valid keyword argument"
except TypeError, msg:
raise TypeError, msg
# http://wiki.python.org/moin/PythonDecoratorLibrary#Type_Enforcement_.28accepts.2Freturns.29
# Slightly modified to always raise an error
def accepts(*types):
'''Function decorator. Checks decorated function's arguments are
of the expected types.
Parameters:
types -- The expected types of the inputs to the decorated function.
Must specify type for each parameter.
'''
try:
def decorator(f):
def newf(*args):
if len(args) != len(types):
raise TypeError("Wrong number of arguments. Expected is %s, given is %s" % (len(types), len(args)))
argtypes = tuple(map(type, args))
# if argtypes != types:
if not compatible_input_types(argtypes, types):
msg = info(f.__name__, types, argtypes, 0)
raise TypeError, msg
return f(*args)
newf.__name__ = f.__name__
return newf
return decorator
except KeyError, key:
raise KeyError, key + "is not a valid keyword argument"
except TypeError, msg:
raise TypeError, msg
# http://wiki.python.org/moin/PythonDecoratorLibrary#Type_Enforcement_.28accepts.2Freturns.29
# Slightly modified to always raise an error
def returns(ret_type):
'''Function decorator. Checks decorated function's return value
is of the expected type.
Parameters:
ret_type -- The expected type of the decorated function's return value.
Must specify type for each parameter.
'''
try:
def decorator(f):
def newf(*args):
result = f(*args)
res_type = type(result)
# print "ret_type=%s, res_type=%s" % (ret_type, res_type)
# if res_type != ret_type:
if not compatible_type(res_type, ret_type):
msg = info(f.__name__, (ret_type,), (res_type,), 1)
raise TypeError, msg
return result
newf.__name__ = f.__name__
return newf
return decorator
except KeyError, key:
raise KeyError, key + "is not a valid keyword argument"
except TypeError, msg:
raise TypeError, msg
# http://wiki.python.org/moin/PythonDecoratorLibrary#Type_Enforcement_.28accepts.2Freturns.29
def info(fname, expected, actual, flag):
'''Convenience function returns nicely formatted error/warning msg.'''
format = lambda types: ', '.join([str(t).split("'")[1] for t in types])
expected, actual = format(expected), format(actual)
msg = "'{0}' method ".format( fname )\
+ ("accepts", "returns")[flag] + " ({0}), but ".format(expected)\
+ ("was given", "result is")[flag] + " ({0})".format(actual)
return msg
def check_string(label, value): def check_string(label, value):
if not issubclass(type(value), basestring): if not issubclass(type(value), basestring):
raise Exception( raise Exception(
...@@ -137,8 +16,6 @@ def check_context(context): ...@@ -137,8 +16,6 @@ def check_context(context):
return context return context
@accepts(str, str)
@returns(bool)
def is_abs_name(name, label='name'): def is_abs_name(name, label='name'):
check_string(label, name) check_string(label, name)
return (name == 'system') or name.startswith('system/') return (name == 'system') or name.startswith('system/')
...@@ -221,8 +98,6 @@ def level_of_node(node_name): ...@@ -221,8 +98,6 @@ def level_of_node(node_name):
len(node_name.split('/')) - 1 len(node_name.split('/')) - 1
@accepts(str, str)
@returns(bool)
def is_child_of_abs_name(child, parent): def is_child_of_abs_name(child, parent):
check_abs_name(parent) check_abs_name(parent)
return child.startswith(parent) and child != parent return child.startswith(parent) and child != parent
...@@ -254,8 +129,6 @@ def parent_abs_name_of(abs_name, label='abs_name'): ...@@ -254,8 +129,6 @@ def parent_abs_name_of(abs_name, label='abs_name'):
return upto_name return upto_name