Seed code for yarf
[ta/yarf.git] / README.rst
diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..707039a
--- /dev/null
@@ -0,0 +1,402 @@
+::
+
+   Copyright 2019 Nokia
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+=========================================
+YARF REST framework
+=========================================
+
+:Author: Janne Suominen
+
+.. raw:: pdf
+
+    PageBreak
+
+.. sectnum::
+
+.. contents::
+
+.. raw:: pdf
+
+    PageBreak
+
+
+Introduction
+============
+
+What is REST
+------------
+**Representational state transfer** (REST) or **RESTful** web services is a way of providing 
+interoperability between computer systems on the Internet. REST-compliant Web services allow 
+requesting systems to access and manipulate textual representations of Web resources using a 
+uniform and predefined set of stateless operations.
+
+About this project
+------------------
+
+YARF --- Yet Another Restful Framework
+
+This project provides an implementation for a generic rest framework The
+framework is based on
+`Flask-RESTful <https://flask-restful.readthedocs.io/en/0.3.5/>`__ 
+
+|  
+
+*The framework provides the following:*
+
+* A generic rest server for whole cloud 
+
+* A plugin based interface for creating rest apis.
+
+|  
+
+*At the high level, the framework serves the following purposes*:
+
+* Provides a unified interface for creating rest interfaces. 
+
+* Single point of entry with plugin based authentication for apis. 
+
+* Define the rest response and query format.
+
+| 
+
+Requirements
+------------
+
+* Framework shall provide easy way to integrate new REST apis via plugins
+
+* Framework shall be integrated to the CM and provide means to automatically configure itself
+
+* Framework shall listen to management address of controller on all the controllers
+
+* Framework shall listen to external and internal load balancer address
+
+* Framework shall provide means to configure SSL certification
+
+* Framework shall be integrated to Authentication mechanism provided by the platform
+
+* Framework shall provide means to validate the parameters given as part of the request
+
+* Framework shall return an error code in case of:
+    
+    * Internal failure; Any failure within the module is considered as internal failure
+
+    * Authentication failure; When authentication has failed or missing credentials
+
+    * Not found; When the object is not found
+
+Structure of data
+=================
+
+The implementation of this framework promotes returning JSON format
+objects with key value pairs.
+
+Structure of the operation
+--------------------------
+
+The framework supports adding function calls for any HTTP requests. The
+request can either: 
+
+* Have a request in the body of the message as JSON
+
+* Have a request embedded in the url
+
+Structure of response
+---------------------
+
+This framework does not enforce any special structure for the response,
+but **it's strongly** encouraged to use the following formatting for the
+response:
+
+.. code:: json
+
+     { 
+       code: <INT>
+       description: <STR>
+       data: <DICT>
+     }
+
+Where: 
+
+* code is the return value of the api in question. 
+
+  * 0 means no error and anything other is considered as failure 
+
+* description is the description of the possible failure (can be left empty in case there is no failure) 
+
+* data is the data returned by the api. The data should be in dictionary (JSON) format
+
+The reasoning for the quite strict guidelines is: 
+
+* Uniqueness of the response makes it easier for the upper level to check the response
+
+**Note**: The framework will return HTTP status code that is not 200 in
+case of: 
+
+* 500: Internal failure (ie. Uncaught exception from the plugin) 
+
+* 401: In case of authentication failure (if authentication is defined)
+
+* 404: In case the object requested is not found
+
+High level architecture of the restful framework
+================================================
+
+At the high level, there is a layer built on top of flask-restful to be
+able to: 
+
+* Isolation of the framework implementation details. 
+
+* To be able to provide more specific implementation to fit to our needs. 
+
+* To be able to make a single point of entry to the clusters rest api 
+
+* Flexibility to change different parts of the implementation without affecting the users of the framework. 
+
+* Provide unique responses to caller for easy parsing
+
+
+.. figure:: ./design/architecture.jpeg
+   :alt: architecture
+
+   architecture
+
+Restful framework interfaces
+============================
+
+RestResource
+------------
+
+All the plugins have to inherit from this class. This class is the basis
+of the plugin framework. All the plugins that inherit from this object
+and are defined in plugin specific inifile will be automatically
+imported.
+
+The http requests will be converted to corresponding lowercase
+functions. For example: request method GET will call *get()* function and
+POST will call *post()*.
+
+The resource should also define endpoints where it want's to register
+these calls. This is done by setting the *endpoint* class variable list.
+
+
+For decorating functions with decorators *extra_wrappers* can be used.
+The function must return either the function or a dictionary that is of
+the same format defined in `Structure of Response`_.
+
+To have authentication for the module adding class variable named
+*authentication_method* needs to be defined. For production environment
+this should be left untouched since the authentication should be controlled
+centrally by the framework.
+
+For logging there is a class variable called *logger* that works like a
+normal logger.
+
+An example of a plugin can look like this:
+
+.. _test_rest:
+.. code:: python
+
+    class TestRest(RestResource):
+        endpoints = ['test'] 
+        def get(self):
+            self.logger.debug("Got get request")
+            return {"code": 0, "description": "", "data": "Foobar"}
+
+Arguments:
+~~~~~~~~~~
+
+For parsing the arguments from the message body there function defined in `RestResource`_
+called *get_args*. This function will return the arguments that are in the request.
+The parser needs to be initialized with *parser_arguments* variable that is a list of
+variables your module want's to parse. 
+
+If one needs to define more complex type of an argument it can be done with the help of *RequestArgument*.
+This class provides the means of:
+    
+    * Setting a default value
+    
+    * Validation of the value by callback function
+    
+    * The type of the value
+
+When this type of argument is passed as one (or more) of the values. The validation
+will be automatically triggered when calling the *get_args* from the *RestResource*.
+
+
+BaseAuth
+--------
+
+This class defines the Base for the authentication.
+
+The class needs to define function *is_authenticated*. The function
+gets the request as an argument.
+
+This function will be called when a plugin has specified the
+authentication method as a derived class of BaseAuth.
+
+Here is an example of a very simple authentication class.
+
+.. code:: python
+
+    from base_auth import BaseAuthMethod
+
+    class TextBase(BaseAuthMethod):
+        def __init__(self):
+            super(TextBase, self).__init__()
+            self.user = ''
+            self.password = ''
+            with open('/tmp/foo') as f:
+                self.user, self.password = f.read().strip().split(':')
+
+        def is_authenticated(self, request):
+            if request.authorization and request.authorization.username == self.user and request.authorization.password == self.password:
+                return True
+            return False
+
+Keystone
+~~~~~~~~
+
+For keystone additional configuration is needed: 
+
+* User with admin role needs to be added (or admin used)
+
+* The config.ini has to contain the credentials and the url of keystone
+
+The following configuration needs to be added to config.ini
+
+.. code:: ini
+
+    [keystone]
+    user=restful
+    password=foobar
+    auth_uri=http://192.168.1.15:5000
+
+After the configuration is done and the authentication will be needed
+then the http headers have to contain token with admin privileges as
+X-Auth-Token.
+
+Restful framework binary
+========================
+
+The framework has only one binary. It's called restapi. The server will
+be automatically started during the deployment.
+
+restapi config file
+-------------------
+
+The default configuration file for the restful server is located at
+/etc/yarf/config.ini
+
+To override the default config file, restapi can be started with command
+line parameter --config. This allows testing plugins without
+interference to the rest of the system.
+
+The config file contains the following parameters:
+
+.. code:: ini
+
+    [restframe]
+
+    #The port that the restful app will listen DEFAULT:61200
+    port=61200
+    #The IP address that the restful app will bind to DEFAULT:127.0.0.1
+    ip_address=127.0.0.1
+
+
+    #Use SSL or not
+    #If true then private key and certificate has to be also given DEFAULT:False
+    use_ssl=false
+    #ssl_private_key=PATHTOKEY/KEY.key
+    #ssl_certificate=PATHTOCERTIFICATE/CERT.crt
+
+    #The directory where the handlers are 
+    #Defaults to /opt/yarf/handlers
+    handler_directory=/opt/yarf/handlers
+
+The configuration file will be generated with an ansible module that will configure the framework.
+Restapi service will run on all the controllers and listen to the controller internal management IP.
+HAProxy will be configured so that clients can take a connection to the internal loadbalancer address 
+(or internal VIP) or external loadbalancer address (external VIP). 
+The framework will listen to port 61200.
+
+Creating plugins
+================
+
+First of all you need to have your own Class defined like the described
+in `RestResource`_. The second thing needed is an ini file that describes
+the handlers for different requests and the api version of the handler.
+
+The plugins should be placed in their own directory under :
+
+/opt/yarf/handlers.
+
+The thing that needs to be remembered that the object lifetime of *RestResource* is
+and will be only the duration of the query. Any data stored by that query that is needed
+cannot be stored in the internal variables of the module. This is anyway against the 
+*statelessness* nature of rest.
+
+Ini file for plugin
+-------------------
+
+The recommendation is to have your own directory (although not mandatory)
+per plugin. Within that directory you have to create an inifile that is
+of the following format:
+
+.. code:: ini
+
+    [<API_VERSION>]
+    handlers=<YOUR_HANDLER>
+
+Where the name of the inifile will the first part of your path on the
+rest server. API_VERSION the second And the handler endpoint(s) the
+third.
+
+For example if one would create an inifile for the `test_rest`_ resource
+it would look like this: testing.ini:
+
+.. code:: ini
+
+    [v1]
+    handlers=TestRest
+
+Then if you want to test your api you could do that with curl:
+
+curl http://testing/test/v1/test
+
+.. code:: json
+
+    {
+        "code": 0, 
+        "description": "u''"
+        "data": "u'Foobar'"
+    }
+
+There is also a helper to check the apis and their locations:
+
+curl http://testing/test/apis
+
+.. code:: json
+
+    [
+      {
+        "href": "http://testing/test/v1", 
+        "id": "v1"
+      } 
+    ]
+
+