Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / commissioning / callpoint.py @ b4368e33

History | View | Annotate | Download (8.2 kB)

1
# Copyright 2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34

    
35
from kamaki.clients.commissioning.specificator import CanonifyException
36
from kamaki.clients.commissioning.exception import CorruptedError
37
from kamaki.clients.commissioning.exception import InvalidDataError
38
from kamaki.clients.commissioning.exception import ReturnButFail
39
from kamaki.clients.commissioning.importing import  imp_module
40

    
41
from re import compile as re_compile, sub as re_sub
42

    
43

    
44
class Callpoint(object):
45

    
46
    api_spec = None
47

    
48
    CorruptedError = CorruptedError
49
    InvalidDataError = InvalidDataError
50

    
51
    original_calls = None
52

    
53
    def __init__(self, connection=None):
54
        from json import loads, dumps
55

    
56
        self.json_loads = loads
57
        self.json_dumps = dumps
58
        self.init_connection(connection)
59
        original_calls = {}
60
        self.original_calls = original_calls
61
        canonifier = self.api_spec
62

    
63
        if canonifier is None:
64
            m = "No api spec given to '%s'" % (type(self).__name__,)
65
            raise NotImplementedError(m)
66

    
67
        for call_name, call_doc in canonifier.call_docs():
68
            if hasattr(self, call_name):
69
                # don't crash: wrap the function instead
70
                #m = (   "Method '%s' defined both in natively "
71
                #        "in callpoint '%s' and in api spec '%s'" %
72
                #            (call_name,
73
                #             type(self).__name__,
74
                #             type(canonifier).__name__)             )
75

    
76
                #raise ValueError(m)
77
                call_func = getattr(self, call_name)
78
                if not callable(call_func):
79
                    m = (   "api spec '%s', method '%s' is not a "
80
                            "callable attribute in callpoint '%s'" % 
81
                            (   type(canonifier).__name__,
82
                                call_name,
83
                                type(self).__name       )           )
84
                    raise ValueError(m)
85

    
86
                original_calls[call_name] = call_func
87

    
88
            def mk_call_func():
89
                local_call_name = call_name
90
                def call_func(**data):
91
                    return self.make_call(local_call_name, data)
92

    
93
                call_func.__name__ = call_name
94
                call_func.__doc__ = call_doc
95
                return call_func
96

    
97
            setattr(self, call_name, mk_call_func())
98

    
99
    def init_connection(self, connection):
100
        pass
101

    
102
    def commit(self):
103
        pass
104

    
105
    def rollback(self):
106
        pass
107

    
108
    def do_make_call(self, call_name, data):
109
        raise NotImplementedError
110

    
111
    def validate_call(self, call_name):
112
        return hasattr(self, call_name)
113

    
114
    def make_call_from_json_description(self, json_description):
115
        try:
116
            description = self.json_loads(json_description)
117
        except ValueError, e:
118
            m = "Cannot load json description"
119
            raise self.InvalidDataError(m)
120

    
121
        data = self.make_call_from_description(description)
122
        json_data = self.json_dumps(data) if data is not None else None
123
        return json_data
124

    
125
    def make_call_from_description(self, description):
126
        try:
127
            call_name = description['call_name']
128
            call_data = description['call_data']
129
        except (TypeError, KeyError), e:
130
            m = "Invalid description"
131
            raise self.InvalidDataError(m, e)
132

    
133
        return self.make_call(call_name, call_data)
134

    
135
    def make_call_from_json(self, call_name, json_data):
136
        if json_data:
137
            try:
138
                data = self.json_loads(json_data)
139
            except ValueError, e:
140
                m = "Cannot load json data"
141
                raise self.InvalidDataError(m, e)
142
        else:
143
            data = None
144

    
145
        data = self.make_call(call_name, data)
146
        json_data = self.json_dumps(data)
147
        return json_data
148

    
149
    def make_call(self, call_name, data):
150
        if call_name.startswith('_'):
151
            m = "Invalid call '%s'" % (call_name,)
152
            raise self.InvalidDataError(m)
153

    
154
        canonifier = self.api_spec
155
        try:
156
            data = canonifier.canonify_input(call_name, data)
157
        except CanonifyException, e:
158
            m = "Invalid input to call '%s'" % (call_name,)
159
            raise self.InvalidDataError(m, e)
160

    
161
        if not self.validate_call(call_name):
162
            m = "Cannot find specified call '%s'" % (call_name,)
163
            raise self.CorruptedError(m)
164

    
165
        call_func = self.original_calls.get(call_name, None)
166
        try:
167
            if call_func is None:
168
                data = self.do_make_call(call_name, data)
169
            else:
170
                data = call_func(**data)
171
            self.commit()
172
        except ReturnButFail, e:
173
            self.rollback()
174
            data = e.data
175
        except Exception, e:
176
            self.rollback()
177
            raise
178

    
179
        try:
180
            data = canonifier.canonify_output(call_name, data)
181
        except CanonifyException, e:
182
            m = "Invalid output from call '%s'" % (call_name,)
183
            raise self.CorruptedError(m, e)
184

    
185
        return data
186

    
187

    
188
def mkcallargs(**kw):
189
    return kw
190

    
191

    
192
versiontag_pattern = re_compile('[^a-zA-Z0-9_-]')
193

    
194
def mk_versiontag(version):
195
    if not version or version == 'v':
196
        return ''
197

    
198
    return '_' + re_sub(versiontag_pattern, '_', version)
199

    
200

    
201
def get_callpoint(pointname, version=None, automake=None, **kw):
202

    
203
    versiontag = mk_versiontag(version)
204
    components = pointname.split('.')
205

    
206
    appname = components[0]
207
    if len(components) < 2:
208
        raise ValueError("invalid pointname '%s'" % (pointname,))
209

    
210
    category = components[1]
211
    if not category or category not in ['clients', 'servers']:
212
        raise ValueError("invalid pointname '%s'" % (pointname,))
213

    
214
    modname = ('%s.callpoint.API_Callpoint%s' 
215
                                            % (pointname, versiontag))
216

    
217
    try:
218
        API_Callpoint = imp_module(modname)
219
        return API_Callpoint
220
    except ImportError:
221
        if not automake:
222
            raise
223

    
224
    if category != 'clients':
225
        m = ("Can only auto-make callpoint in 'clients' not '%s'" % (category,))
226
        raise ValueError(m)
227

    
228
    components = components[1:]
229
    if not components:
230
        raise ValueError("invalid pointname '%s'" % (pointname))
231

    
232
    pointname = '.'.join(components)
233
    if pointname == 'quotaholder':
234
        apiname = 'quotaholder.api.QuotaholderAPI'
235
    else:
236
        apiname = '%s.api.API_Spec%s' % (pointname, versiontag)
237

    
238
    API_Spec = imp_module(apiname)
239

    
240
    basename = 'commissioning.clients.%s.API_Callpoint' % (automake,)
241
    BaseCallpoint = imp_module(basename)
242

    
243
    stupidpython = (appname,
244
                    version if version is not None else 'v',
245
                    pointname,
246
                    automake)
247

    
248
    class AutoCallpoint(BaseCallpoint):
249
        appname, version, pointname, automake = stupidpython
250
        api_spec = API_Spec()
251

    
252
    return AutoCallpoint
253