Statistics
| Branch: | Tag: | Revision:

root / snf-common / synnefo / lib / commissioning / callpoint.py @ c05b0cbe

History | View | Annotate | Download (8 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 .specificator  import  CanonifyException
36
from .exception     import  CorruptedError, InvalidDataError
37
from .importing     import  imp_module
38

    
39
from re import compile as re_compile, sub as re_sub
40

    
41
class Callpoint(object):
42

    
43
    api_spec = None
44

    
45
    CorruptedError = CorruptedError
46
    InvalidDataError = InvalidDataError
47

    
48
    original_calls = None
49

    
50
    def __init__(self, connection=None):
51
        from json import loads, dumps
52

    
53
        self.json_loads = loads
54
        self.json_dumps = dumps
55
        self.init_connection(connection)
56
        original_calls = {}
57
        self.original_calls = original_calls
58
        canonifier = self.api_spec
59

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

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

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

    
83
                original_calls[call_name] = call_func
84

    
85
            def mk_call_func():
86
                local_call_name = call_name
87
                def call_func(**data):
88
                    return self.make_call(local_call_name, data)
89

    
90
                call_func.__name__ = call_name
91
                call_func.__doc__ = call_doc
92
                return call_func
93

    
94
            setattr(self, call_name, mk_call_func())
95

    
96
    def init_connection(self, connection):
97
        pass
98

    
99
    def commit(self):
100
        pass
101

    
102
    def rollback(self):
103
        pass
104

    
105
    def do_make_call(self, call_name, data):
106
        raise NotImplementedError
107

    
108
    def validate_call(self, call_name):
109
        return hasattr(self, call_name)
110

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

    
118
        data = self.make_call_from_description(description)
119
        json_data = self.json_dumps(data) if data is not None else None
120
        return json_data
121

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

    
130
        return self.make_call(call_name, call_data)
131

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

    
142
        data = self.make_call(call_name, data)
143
        json_data = self.json_dumps(data)
144
        return json_data
145

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

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

    
158
        if not self.validate_call(call_name):
159
            m = "Cannot find specified call '%s'" % (call_name,)
160
            raise self.CorruptedError(m)
161

    
162
        call_func = self.original_calls.get(call_name, None)
163
        try:
164
            if call_func is None:
165
                data = self.do_make_call(call_name, data)
166
            else:
167
                data = call_func(**data)
168
        except Exception, e:
169
            self.rollback()
170
            raise
171
        else:
172
            self.commit()
173

    
174
        try:
175
            data = canonifier.canonify_output(call_name, data)
176
        except CanonifyException, e:
177
            m = "Invalid output from call '%s'" % (call_name,)
178
            raise self.CorruptedError(m, e)
179

    
180
        return data
181

    
182

    
183
def mkcallargs(**kw):
184
    return kw
185

    
186

    
187
versiontag_pattern = re_compile('[^a-zA-Z0-9_-]')
188

    
189
def mk_versiontag(version):
190
    if not version or version == 'v':
191
        return ''
192

    
193
    return '_' + re_sub(versiontag_pattern, '_', version)
194

    
195

    
196
def get_callpoint(pointname, version=None, automake=None, **kw):
197

    
198
    versiontag = mk_versiontag(version)
199
    components = pointname.split('.')
200

    
201
    appname = components[0]
202
    if len(components) < 2:
203
        raise ValueError("invalid pointname '%s'" % (pointname,))
204

    
205
    category = components[1]
206
    if not category or category not in ['clients', 'servers']:
207
        raise ValueError("invalid pointname '%s'" % (pointname,))
208

    
209
    modname = ('%s.callpoint.API_Callpoint%s' 
210
                                            % (pointname, versiontag))
211

    
212
    try:
213
        API_Callpoint = imp_module(modname)
214
        return API_Callpoint
215
    except ImportError:
216
        if not automake:
217
            raise
218

    
219
    if category != 'clients':
220
        m = ("Can only auto-make callpoint in 'clients' not '%s'" % (category,))
221
        raise ValueError(m)
222

    
223
    components = components[1:]
224
    if not components:
225
        raise ValueError("invalid pointname '%s'" % (pointname))
226

    
227
    pointname = '.'.join(components)
228
    if pointname == 'quotaholder':
229
        apiname = 'quotaholder.api.QuotaholderAPI'
230
    else:
231
        apiname = '%s.api.API_Spec%s' % (pointname, versiontag)
232

    
233
    API_Spec = imp_module(apiname)
234

    
235
    basename = 'commissioning.clients.%s.API_Callpoint' % (automake,)
236
    BaseCallpoint = imp_module(basename)
237

    
238
    stupidpython = (appname,
239
                    version if version is not None else 'v',
240
                    pointname,
241
                    automake)
242

    
243
    class AutoCallpoint(BaseCallpoint):
244
        appname, version, pointname, automake = stupidpython
245
        api_spec = API_Spec()
246

    
247
    return AutoCallpoint
248