5 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
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.
19 =========================================
21 =========================================
23 :Author: Janne Suominen
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.
51 YARF --- Yet Another Restful Framework
53 This project provides an implementation for a generic rest framework The
55 `Flask-RESTful <https://flask-restful.readthedocs.io/en/0.3.5/>`__
59 *The framework provides the following:*
61 * A generic rest server for whole cloud
63 * A plugin based interface for creating rest apis.
67 *At the high level, the framework serves the following purposes*:
69 * Provides a unified interface for creating rest interfaces.
71 * Single point of entry with plugin based authentication for apis.
73 * Define the rest response and query format.
80 * Framework shall provide easy way to integrate new REST apis via plugins
82 * Framework shall be integrated to the CM and provide means to automatically configure itself
84 * Framework shall listen to management address of controller on all the controllers
86 * Framework shall listen to external and internal load balancer address
88 * Framework shall provide means to configure SSL certification
90 * Framework shall be integrated to Authentication mechanism provided by the platform
92 * Framework shall provide means to validate the parameters given as part of the request
94 * Framework shall return an error code in case of:
96 * Internal failure; Any failure within the module is considered as internal failure
98 * Authentication failure; When authentication has failed or missing credentials
100 * Not found; When the object is not found
105 The implementation of this framework promotes returning JSON format
106 objects with key value pairs.
108 Structure of the operation
109 --------------------------
111 The framework supports adding function calls for any HTTP requests. The
114 * Have a request in the body of the message as JSON
116 * Have a request embedded in the url
118 Structure of response
119 ---------------------
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
135 * code is the return value of the api in question.
137 * 0 means no error and anything other is considered as failure
139 * description is the description of the possible failure (can be left empty in case there is no failure)
141 * data is the data returned by the api. The data should be in dictionary (JSON) format
143 The reasoning for the quite strict guidelines is:
145 * Uniqueness of the response makes it easier for the upper level to check the response
147 **Note**: The framework will return HTTP status code that is not 200 in
150 * 500: Internal failure (ie. Uncaught exception from the plugin)
152 * 401: In case of authentication failure (if authentication is defined)
154 * 404: In case the object requested is not found
156 High level architecture of the restful framework
157 ================================================
159 At the high level, there is a layer built on top of flask-restful to be
162 * Isolation of the framework implementation details.
164 * To be able to provide more specific implementation to fit to our needs.
166 * To be able to make a single point of entry to the clusters rest api
168 * Flexibility to change different parts of the implementation without affecting the users of the framework.
170 * Provide unique responses to caller for easy parsing
173 .. figure:: ./design/architecture.jpeg
178 Restful framework interfaces
179 ============================
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
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()*.
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.
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`_.
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.
206 For logging there is a class variable called *logger* that works like a
209 An example of a plugin can look like this:
214 class TestRest(RestResource):
217 self.logger.debug("Got get request")
218 return {"code": 0, "description": "", "data": "Foobar"}
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.
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:
231 * Setting a default value
233 * Validation of the value by callback function
235 * The type of the value
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*.
244 This class defines the Base for the authentication.
246 The class needs to define function *is_authenticated*. The function
247 gets the request as an argument.
249 This function will be called when a plugin has specified the
250 authentication method as a derived class of BaseAuth.
252 Here is an example of a very simple authentication class.
256 from base_auth import BaseAuthMethod
258 class TextBase(BaseAuthMethod):
260 super(TextBase, self).__init__()
263 with open('/tmp/foo') as f:
264 self.user, self.password = f.read().strip().split(':')
266 def is_authenticated(self, request):
267 if request.authorization and request.authorization.username == self.user and request.authorization.password == self.password:
274 For keystone additional configuration is needed:
276 * User with admin role needs to be added (or admin used)
278 * The config.ini has to contain the credentials and the url of keystone
280 The following configuration needs to be added to config.ini
287 auth_uri=http://192.168.1.15:5000
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
293 Restful framework binary
294 ========================
296 The framework has only one binary. It's called restapi. The server will
297 be automatically started during the deployment.
302 The default configuration file for the restful server is located at
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.
309 The config file contains the following parameters:
315 #The port that the restful app will listen DEFAULT:61200
317 #The IP address that the restful app will bind to DEFAULT:127.0.0.1
322 #If true then private key and certificate has to be also given DEFAULT:False
324 #ssl_private_key=PATHTOKEY/KEY.key
325 #ssl_certificate=PATHTOCERTIFICATE/CERT.crt
327 #The directory where the handlers are
328 #Defaults to /opt/yarf/handlers
329 handler_directory=/opt/yarf/handlers
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.
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.
344 The plugins should be placed in their own directory under :
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.
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:
363 handlers=<YOUR_HANDLER>
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
369 For example if one would create an inifile for the `test_rest`_ resource
370 it would look like this: testing.ini:
377 Then if you want to test your api you could do that with curl:
379 curl http://testing/test/v1/test
389 There is also a helper to check the apis and their locations:
391 curl http://testing/test/apis
397 "href": "http://testing/test/v1",