Seed code for yarf
[ta/yarf.git] / README.rst
1 ::
2
3    Copyright 2019 Nokia
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6
7    you may not use this file except in compliance with the License.
8    You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12    Unless required by applicable law or agreed to in writing, software
13    distributed under the License is distributed on an "AS IS" BASIS,
14    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15    See the License for the specific language governing permissions and
16    limitations under the License.
17
18
19 =========================================
20 YARF REST framework
21 =========================================
22
23 :Author: Janne Suominen
24
25 .. raw:: pdf
26
27     PageBreak
28
29 .. sectnum::
30
31 .. contents::
32
33 .. raw:: pdf
34
35     PageBreak
36
37
38 Introduction
39 ============
40
41 What is REST
42 ------------
43 **Representational state transfer** (REST) or **RESTful** web services is a way of providing 
44 interoperability between computer systems on the Internet. REST-compliant Web services allow 
45 requesting systems to access and manipulate textual representations of Web resources using a 
46 uniform and predefined set of stateless operations.
47
48 About this project
49 ------------------
50
51 YARF --- Yet Another Restful Framework
52
53 This project provides an implementation for a generic rest framework The
54 framework is based on
55 `Flask-RESTful <https://flask-restful.readthedocs.io/en/0.3.5/>`__ 
56
57 |  
58
59 *The framework provides the following:*
60
61 * A generic rest server for whole cloud 
62
63 * A plugin based interface for creating rest apis.
64
65 |  
66
67 *At the high level, the framework serves the following purposes*:
68
69 * Provides a unified interface for creating rest interfaces. 
70
71 * Single point of entry with plugin based authentication for apis. 
72
73 * Define the rest response and query format.
74
75
76
77 Requirements
78 ------------
79
80 * Framework shall provide easy way to integrate new REST apis via plugins
81
82 * Framework shall be integrated to the CM and provide means to automatically configure itself
83
84 * Framework shall listen to management address of controller on all the controllers
85
86 * Framework shall listen to external and internal load balancer address
87
88 * Framework shall provide means to configure SSL certification
89
90 * Framework shall be integrated to Authentication mechanism provided by the platform
91
92 * Framework shall provide means to validate the parameters given as part of the request
93
94 * Framework shall return an error code in case of:
95     
96     * Internal failure; Any failure within the module is considered as internal failure
97
98     * Authentication failure; When authentication has failed or missing credentials
99
100     * Not found; When the object is not found
101
102 Structure of data
103 =================
104
105 The implementation of this framework promotes returning JSON format
106 objects with key value pairs.
107
108 Structure of the operation
109 --------------------------
110
111 The framework supports adding function calls for any HTTP requests. The
112 request can either: 
113
114 * Have a request in the body of the message as JSON
115
116 * Have a request embedded in the url
117
118 Structure of response
119 ---------------------
120
121 This framework does not enforce any special structure for the response,
122 but **it's strongly** encouraged to use the following formatting for the
123 response:
124
125 .. code:: json
126
127      { 
128        code: <INT>
129        description: <STR>
130        data: <DICT>
131      }
132
133 Where: 
134
135 * code is the return value of the api in question. 
136
137   * 0 means no error and anything other is considered as failure 
138
139 * description is the description of the possible failure (can be left empty in case there is no failure) 
140
141 * data is the data returned by the api. The data should be in dictionary (JSON) format
142
143 The reasoning for the quite strict guidelines is: 
144
145 * Uniqueness of the response makes it easier for the upper level to check the response
146
147 **Note**: The framework will return HTTP status code that is not 200 in
148 case of: 
149
150 * 500: Internal failure (ie. Uncaught exception from the plugin) 
151
152 * 401: In case of authentication failure (if authentication is defined)
153
154 * 404: In case the object requested is not found
155
156 High level architecture of the restful framework
157 ================================================
158
159 At the high level, there is a layer built on top of flask-restful to be
160 able to: 
161
162 * Isolation of the framework implementation details. 
163
164 * To be able to provide more specific implementation to fit to our needs. 
165
166 * To be able to make a single point of entry to the clusters rest api 
167
168 * Flexibility to change different parts of the implementation without affecting the users of the framework. 
169
170 * Provide unique responses to caller for easy parsing
171
172
173 .. figure:: ./design/architecture.jpeg
174    :alt: architecture
175
176    architecture
177
178 Restful framework interfaces
179 ============================
180
181 RestResource
182 ------------
183
184 All the plugins have to inherit from this class. This class is the basis
185 of the plugin framework. All the plugins that inherit from this object
186 and are defined in plugin specific inifile will be automatically
187 imported.
188
189 The http requests will be converted to corresponding lowercase
190 functions. For example: request method GET will call *get()* function and
191 POST will call *post()*.
192
193 The resource should also define endpoints where it want's to register
194 these calls. This is done by setting the *endpoint* class variable list.
195
196
197 For decorating functions with decorators *extra_wrappers* can be used.
198 The function must return either the function or a dictionary that is of
199 the same format defined in `Structure of Response`_.
200
201 To have authentication for the module adding class variable named
202 *authentication_method* needs to be defined. For production environment
203 this should be left untouched since the authentication should be controlled
204 centrally by the framework.
205
206 For logging there is a class variable called *logger* that works like a
207 normal logger.
208
209 An example of a plugin can look like this:
210
211 .. _test_rest:
212 .. code:: python
213
214     class TestRest(RestResource):
215         endpoints = ['test'] 
216         def get(self):
217             self.logger.debug("Got get request")
218             return {"code": 0, "description": "", "data": "Foobar"}
219
220 Arguments:
221 ~~~~~~~~~~
222
223 For parsing the arguments from the message body there function defined in `RestResource`_
224 called *get_args*. This function will return the arguments that are in the request.
225 The parser needs to be initialized with *parser_arguments* variable that is a list of
226 variables your module want's to parse. 
227
228 If one needs to define more complex type of an argument it can be done with the help of *RequestArgument*.
229 This class provides the means of:
230     
231     * Setting a default value
232     
233     * Validation of the value by callback function
234     
235     * The type of the value
236
237 When this type of argument is passed as one (or more) of the values. The validation
238 will be automatically triggered when calling the *get_args* from the *RestResource*.
239
240
241 BaseAuth
242 --------
243
244 This class defines the Base for the authentication.
245
246 The class needs to define function *is_authenticated*. The function
247 gets the request as an argument.
248
249 This function will be called when a plugin has specified the
250 authentication method as a derived class of BaseAuth.
251
252 Here is an example of a very simple authentication class.
253
254 .. code:: python
255
256     from base_auth import BaseAuthMethod
257
258     class TextBase(BaseAuthMethod):
259         def __init__(self):
260             super(TextBase, self).__init__()
261             self.user = ''
262             self.password = ''
263             with open('/tmp/foo') as f:
264                 self.user, self.password = f.read().strip().split(':')
265
266         def is_authenticated(self, request):
267             if request.authorization and request.authorization.username == self.user and request.authorization.password == self.password:
268                 return True
269             return False
270
271 Keystone
272 ~~~~~~~~
273
274 For keystone additional configuration is needed: 
275
276 * User with admin role needs to be added (or admin used)
277
278 * The config.ini has to contain the credentials and the url of keystone
279
280 The following configuration needs to be added to config.ini
281
282 .. code:: ini
283
284     [keystone]
285     user=restful
286     password=foobar
287     auth_uri=http://192.168.1.15:5000
288
289 After the configuration is done and the authentication will be needed
290 then the http headers have to contain token with admin privileges as
291 X-Auth-Token.
292
293 Restful framework binary
294 ========================
295
296 The framework has only one binary. It's called restapi. The server will
297 be automatically started during the deployment.
298
299 restapi config file
300 -------------------
301
302 The default configuration file for the restful server is located at
303 /etc/yarf/config.ini
304
305 To override the default config file, restapi can be started with command
306 line parameter --config. This allows testing plugins without
307 interference to the rest of the system.
308
309 The config file contains the following parameters:
310
311 .. code:: ini
312
313     [restframe]
314
315     #The port that the restful app will listen DEFAULT:61200
316     port=61200
317     #The IP address that the restful app will bind to DEFAULT:127.0.0.1
318     ip_address=127.0.0.1
319
320
321     #Use SSL or not
322     #If true then private key and certificate has to be also given DEFAULT:False
323     use_ssl=false
324     #ssl_private_key=PATHTOKEY/KEY.key
325     #ssl_certificate=PATHTOCERTIFICATE/CERT.crt
326
327     #The directory where the handlers are 
328     #Defaults to /opt/yarf/handlers
329     handler_directory=/opt/yarf/handlers
330
331 The configuration file will be generated with an ansible module that will configure the framework.
332 Restapi service will run on all the controllers and listen to the controller internal management IP.
333 HAProxy will be configured so that clients can take a connection to the internal loadbalancer address 
334 (or internal VIP) or external loadbalancer address (external VIP). 
335 The framework will listen to port 61200.
336
337 Creating plugins
338 ================
339
340 First of all you need to have your own Class defined like the described
341 in `RestResource`_. The second thing needed is an ini file that describes
342 the handlers for different requests and the api version of the handler.
343
344 The plugins should be placed in their own directory under :
345
346 /opt/yarf/handlers.
347
348 The thing that needs to be remembered that the object lifetime of *RestResource* is
349 and will be only the duration of the query. Any data stored by that query that is needed
350 cannot be stored in the internal variables of the module. This is anyway against the 
351 *statelessness* nature of rest.
352
353 Ini file for plugin
354 -------------------
355
356 The recommendation is to have your own directory (although not mandatory)
357 per plugin. Within that directory you have to create an inifile that is
358 of the following format:
359
360 .. code:: ini
361
362     [<API_VERSION>]
363     handlers=<YOUR_HANDLER>
364
365 Where the name of the inifile will the first part of your path on the
366 rest server. API_VERSION the second And the handler endpoint(s) the
367 third.
368
369 For example if one would create an inifile for the `test_rest`_ resource
370 it would look like this: testing.ini:
371
372 .. code:: ini
373
374     [v1]
375     handlers=TestRest
376
377 Then if you want to test your api you could do that with curl:
378
379 curl http://testing/test/v1/test
380
381 .. code:: json
382
383     {
384         "code": 0, 
385         "description": "u''"
386         "data": "u'Foobar'"
387     }
388
389 There is also a helper to check the apis and their locations:
390
391 curl http://testing/test/apis
392
393 .. code:: json
394
395     [
396       {
397         "href": "http://testing/test/v1", 
398         "id": "v1"
399       } 
400     ]
401
402