Commit 1792ed1d authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Remove quotaholder from kamaki

This affects the cli:
- remove the quotaholder commands set
- remove the commissioning system (automatic command retrieval)

This affevts the clients:
- remove the quotaholder client
- remove the clients commissioning system

Save the ordereddict backport to clients.utils (still useful)

Update documentation and setup
parent bbb9b536
......@@ -64,12 +64,6 @@ file
Hidden command groups
---------------------
quotaholder
A client for quotaholder API. to enable:
kamaki config set quotaholder.cli hotaholder
kamaki config set quotaholder.url <quotaholder server url>
livetest
LIve tests that check kamaki against running services. To enable:
......@@ -220,12 +214,6 @@ file commands
* versions Get the list of object versions
quotaholder commands (hidden)
*****************************
accept, ack, add, create, get, init, issue, list, query, reject, release, reset, resolve, set
test commands (hidden)
**********************
......
......@@ -295,17 +295,6 @@ or a specific method from a service (e.g. astakos authenticate)::
$ kamaki livetest astakos authenticate
The quotaholder client
""""""""""""""""""""""
A quotaholder client is introduced as an advanced feature. Quotaholder client is mostly used as a client library for accessing a synnefo quota service, but it can also be allowed as a kamaki command set, but setting the quotaholder.cli and quotaholder.url methods::
[quotaholder]
cli=quotaholder
url=<URL of quotaholder service>
Quotaholder is not tested in livetest
The unit testing system
"""""""""""""""""""""""
......
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from kamaki.cli.commands import _command_init
from kamaki.cli import command
from kamaki.cli.command_tree import CommandTree
from kamaki.cli.errors import CLIError
from kamaki.clients.commissioning import CanonifyException
class commissioning_cli(object):
api_spec = None
appname = None
client = None
add_context = False
ctree = None
description = None
def __init__(self):
self.api_spec = self.client.api_spec
self.appname = self.client.appname
self.ctree = CommandTree(self.appname, self.description)
def generate_all(self):
for f in self.api_spec.call_names():
c = self.mkClass(f)
command(self.ctree)(c)
def mkClass(self, method):
class C(_command_init):
__doc__ = self.api_spec.get_doc(method)
def init(this):
this.token = (this.config.get(self.appname, 'token') or
this.config.get('global', 'token'))
this.base_url = (this.config.get(self.appname, 'url') or
this.config.get('global', 'url'))
this.client = self.client(this.base_url, this.token)
def call(this, method, args):
ctx = '=null ' if self.add_context else ''
arglist = '[' + ctx + ' '.join(args) + ']'
argdict = self.api_spec.parse(method, arglist)
f = getattr(this.client, method)
return f(**argdict)
def main(this, *args):
this.init()
try:
r = this.call(method, args)
print r
except CanonifyException, e:
params = self.api_spec.show_input_canonical(method)
meth = method.replace('_', ' ')
m = '%s\n usage: %s %s' % (e, meth, params)
raise CLIError('%s: %s\n' % (self.appname, m))
except Exception as e:
raise CLIError('%s: %s\n' % (self.appname, e))
C.__name__ = self.appname + '_' + method
return C
......@@ -39,7 +39,7 @@ from ConfigParser import RawConfigParser, NoOptionError, NoSectionError
try:
from collections import OrderedDict
except ImportError:
from kamaki.clients.commissioning.utils.ordereddict import OrderedDict
from kamaki.clients.utils.ordereddict import OrderedDict
# Path to the file that stores the configuration
......
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
# Import general commission framework
from kamaki.clients.commissioning.exception import (
CallError,
CorruptedError,
InvalidDataError,
ReturnButFail,
register_exception,
register_exceptions)
from kamaki.clients.commissioning.callpoint import (
Callpoint,
mkcallargs)
from kamaki.clients.commissioning.specificator import (
Specificator,
SpecifyException,
Canonifier,
CanonifyException,
Canonical,
Null,
Nothing,
Integer,
Serial,
Text,
Bytes,
Tuple,
ListOf,
Dict,
Args)
# Import standard implementations?
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from kamaki.clients.commissioning.specificator import CanonifyException
from kamaki.clients.commissioning.exception import CorruptedError
from kamaki.clients.commissioning.exception import InvalidDataError
from kamaki.clients.commissioning.exception import ReturnButFail
from kamaki.clients.commissioning.importing import imp_module
from re import compile as re_compile, sub as re_sub
class Callpoint(object):
api_spec = None
CorruptedError = CorruptedError
InvalidDataError = InvalidDataError
original_calls = None
def __init__(self, connection=None):
from json import loads, dumps
self.json_loads = loads
self.json_dumps = dumps
self.init_connection(connection)
original_calls = {}
self.original_calls = original_calls
canonifier = self.api_spec
if canonifier is None:
m = "No api spec given to '%s'" % (type(self).__name__,)
raise NotImplementedError(m)
for call_name, call_doc in canonifier.call_docs():
if hasattr(self, call_name):
#raise ValueError(m)
call_func = getattr(self, call_name)
if not callable(call_func):
raise ValueError(' '.join([
"api spec '%s'," % type(canonifier).__name__,
"method '%s' is not a callable" % call_name,
"attribute in callpoint '%s'" % type(self).__name]))
original_calls[call_name] = call_func
def mk_call_func():
local_call_name = call_name
def call_func(**data):
return self.make_call(local_call_name, data)
call_func.__name__ = call_name
call_func.__doc__ = call_doc
return call_func
setattr(self, call_name, mk_call_func())
def init_connection(self, connection):
pass
def commit(self):
pass
def rollback(self):
pass
def do_make_call(self, call_name, data):
raise NotImplementedError
def validate_call(self, call_name):
return hasattr(self, call_name)
def make_call_from_json_description(self, json_description):
try:
description = self.json_loads(json_description)
except ValueError:
m = "Cannot load json description"
raise self.InvalidDataError(m)
data = self.make_call_from_description(description)
json_data = self.json_dumps(data) if data is not None else None
return json_data
def make_call_from_description(self, description):
try:
call_name = description['call_name']
call_data = description['call_data']
except (TypeError, KeyError), e:
m = "Invalid description"
raise self.InvalidDataError(m, e)
return self.make_call(call_name, call_data)
def make_call_from_json(self, call_name, json_data):
if json_data:
try:
data = self.json_loads(json_data)
except ValueError, e:
m = "Cannot load json data"
raise self.InvalidDataError(m, e)
else:
data = None
data = self.make_call(call_name, data)
json_data = self.json_dumps(data)
return json_data
def make_call(self, call_name, data):
if call_name.startswith('_'):
m = "Invalid call '%s'" % (call_name,)
raise self.InvalidDataError(m)
canonifier = self.api_spec
try:
data = canonifier.canonify_input(call_name, data)
except CanonifyException, e:
m = "Invalid input to call '%s'" % (call_name,)
raise self.InvalidDataError(m, e)
if not self.validate_call(call_name):
m = "Cannot find specified call '%s'" % (call_name,)
raise self.CorruptedError(m)
call_func = self.original_calls.get(call_name, None)
try:
if call_func is None:
data = self.do_make_call(call_name, data)
else:
data = call_func(**data)
self.commit()
except ReturnButFail, e:
self.rollback()
data = e.data
except Exception, e:
self.rollback()
raise
try:
data = canonifier.canonify_output(call_name, data)
except CanonifyException, e:
m = "Invalid output from call '%s'" % (call_name,)
raise self.CorruptedError(m, e)
return data
def mkcallargs(**kw):
return kw
versiontag_pattern = re_compile('[^a-zA-Z0-9_-]')
def mk_versiontag(version):
if not version or version == 'v':
return ''
return '_' + re_sub(versiontag_pattern, '_', version)
def get_callpoint(pointname, version=None, automake=None, **kw):
versiontag = mk_versiontag(version)
components = pointname.split('.')
appname = components[0]
if len(components) < 2:
raise ValueError("invalid pointname '%s'" % (pointname,))
category = components[1]
if not category or category not in ['clients', 'servers']:
raise ValueError("invalid pointname '%s'" % (pointname,))
modname = ('%s.callpoint.API_Callpoint%s' % (pointname, versiontag))
try:
API_Callpoint = imp_module(modname)
return API_Callpoint
except ImportError:
if not automake:
raise
if category != 'clients':
m = (
"Can only auto-make callpoint in 'clients' not '%s'" % (category))
raise ValueError(m)
components = components[1:]
if not components:
raise ValueError("invalid pointname '%s'" % (pointname))
pointname = '.'.join(components)
if pointname == 'quotaholder':
apiname = 'quotaholder.api.QuotaholderAPI'
else:
apiname = '%s.api.API_Spec%s' % (pointname, versiontag)
API_Spec = imp_module(apiname)
basename = 'commissioning.clients.%s.API_Callpoint' % (automake,)
BaseCallpoint = imp_module(basename)
stupidpython = (appname,
version if version is not None else 'v',
pointname,
automake)
class AutoCallpoint(BaseCallpoint):
appname, version, pointname, automake = stupidpython
api_spec = API_Spec()
return AutoCallpoint
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
def str_or_utf8(s):
if isinstance(s, unicode):
return s.encode('utf8')
return str(s)
class CallError(Exception):
exceptions = {}
def __new__(cls, *args, **kw):
call_error = kw.get('call_error', None)
if call_error is None:
call_error = cls.__name__
else:
call_error = str(call_error)
cls = CallError.exceptions.get(call_error, cls)
self = Exception.__new__(cls)
return self
def __init__(self, *args, **kw):
self.call_error = kw.pop('call_error', self.__class__.__name__)
self.args = args
self.kwargs = kw
def __str__(self):
return '\n--------\n'.join(str_or_utf8(x) for x in self.args)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
','.join(str_or_utf8(x) for x in self.args))
@classmethod
def from_exception(cls, exc):
args = None
try:
args = tuple(exc.args)
except (TypeError, AttributeError):
pass
if args is None:
args = (str(exc),)
self = cls(*args, call_error=exc.__class__.__name__)
return self
def to_dict(self):
return {'call_error': self.call_error,
'error_args': (self.args, self.kwargs)}
@classmethod
def from_dict(cls, dictobj):
args = None
try:
if 'error_args' in dictobj and 'call_error' in dictobj:
args = dictobj['error_args']
call_error = dictobj['call_error']
except TypeError:
pass
if args is None:
args = (str(dictobj),)
call_error = 'UnknownError'
kw = {}
else:
args, kw = args
self = cls(*args, call_error=call_error, **kw)
return self
def register_exceptions(*exceptions):
for exception in exceptions:
if not issubclass(exception, CallError):
m = "Registering '%s': is not a CallError subclass" % (exception,)
raise ValueError(m)
CallError.exceptions[exception.__name__] = exception
def register_exception(exc):
register_exceptions(exc)
return exc
@register_exception
class CorruptedError(CallError):
pass
@register_exception
class InvalidDataError(CallError):
pass
class ReturnButFail(Exception):
def __init__(self, retval=None):
self.data = retval
# Copyright 2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,