Commit 54069d1b authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Commisioning and quotaholder clients in kamaki

Quotaholder is not just another client. It comes with code for authomatic
generation of client lib and cli interface.

Copied from kamaki/quota branch but not merged (easier)
parent 65b8765b
# 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.commissioning import commissioning_cli
from kamaki.clients.quotaholder import QuotaholderClient
class quotaholder_cli(commissioning_cli):
def __init__(self):
self.client = QuotaholderClient
self.add_context = True
self.description = 'Quotaholder description'
super(self.__class__, self).__init__()
cli = quotaholder_cli()
cli.generate_all()
_commands = [cli.ctree]
# 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
# 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 .exception import (CallError, CorruptedError, InvalidDataError,
ReturnButFail,
register_exception, register_exceptions)
from .callpoint import Callpoint, mkcallargs
from .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 .specificator import CanonifyException
from .exception import CorruptedError, InvalidDataError, ReturnButFail
from .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):
# don't crash: wrap the function instead
#m = ( "Method '%s' defined both in natively "
# "in callpoint '%s' and in api spec '%s'" %
# (call_name,
# type(self).__name__,
# type(canonifier).__name__) )
#raise ValueError(m)
call_func = getattr(self, call_name)
if not callable(call_func):
m = ( "api spec '%s', method '%s' is not a "
"callable attribute in callpoint '%s'" %
( type(canonifier).__name__,
call_name,
type(self).__name ) )
raise ValueError(m)
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, e:
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.
from kamaki.clients.commissioning import Callpoint, CallError
from kamaki.clients.commissioning.utils.debug import debug
from kamaki.clients import Client
from json import loads as json_loads, dumps as json_dumps
class CommissioningClient(Callpoint):
def __init__(self, base_url, token):
super(CommissioningClient, self).__init__()
self._kc = Client(base_url, token)
def do_make_call(self, api_call, data):
_kc = self._kc
gettable = ['list', 'get', 'read']
method = (_kc.get if any(api_call.startswith(x) for x in gettable)
else _kc.post)
path = api_call
json_data = json_dumps(data)
debug("%s %s\n%s\n<<<\n", method.func_name, path, json_data)
resp = method(path, data=json_data, success=(200,450,500))
debug(">>>\nStatus: %s", resp.status_code)
body = resp.text
debug("\n%s\n<<<\n", body[:128] if body else None)
status = int(resp.status_code)
if status == 200:
return json_loads(body)
else:
try:
error = json_loads(body)
except ValueError, e:
exc = CallError(body, call_error='ValueError')
else:
exc = CallError.from_dict(error)
raise exc
# 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.
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.get('call_error', self.__class__.__name__)
self.args = args
def __str__(self):
return '\n--------\n'.join(str(x) for x in self.args)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__,
','.join(str(x) for x in self.args))
@classmethod
def from_exception(cls, exc):
args = None
try:
args = tuple(exc.args)
except (TypeError, AttributeError), e:
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}
@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, e:
pass
if args is None:
args = (str(dictobj),)
call_error = 'UnknownError'