********************************************** QTIP RESTful Application Programming Interface ********************************************** Abstract ######## QTIP consists of different tools(metrics) to benchmark the NFVI. These metrics fall under different NFVI subsystems(QPI's) such as compute, storage and network. A plan consists of one or more QPI's, depending upon how the end-user would want to measure performance. API is designed to expose a RESTful interface to the user for executing benchmarks and viewing respective scores. Framework ========= QTIP API has been created using the Python package `Connexion`_. It has been chosen for a number of reasons. It follows API First approach to create micro-services. Hence, firstly the API specifications are defined from the client side perspective, followed by the implementation of the micro-service. It decouples the business logic from routing and resource mapping making design and implementation cleaner. It has two major components: API Specifications The API specification is defined in a yaml or json file. Connexion follows `Open API specification`_ to determine the design and maps the endpoints to methods in python. Micro-service Implementation Connexion maps the ``operationId`` corresponding to every operation in API Specification to methods in python which handles request and responses. As explained, QTIP consists of metrics, QPI's and plans. The API is designed to provide a RESTful interface to all these components. It is responsible to provide listing and details of each individual element making up these components. Design ====== Specification ------------- API's entry point (``main``) runs connexion ``App`` class object after adding API Specification using ``App.add_api`` method. It loads specification from ``swagger.yaml`` file by specifying ``specification_dir``. Connexion reads API's endpoints(paths), operations, their request and response parameter details and response definitions from the API specification i.e. ``swagger.yaml`` in this case. Following example demonstrates specification for the resource ``plans``. :: paths: /plans/{name}: get: summary: Get a plan by plan name operationId: qtip.api.controllers.plan.get_plan tags: - Plan - Standalone parameters: - name: name in: path description: Plan name required: true type: string responses: 200: description: Plan information schema: $ref: '#/definitions/Plan' 404: description: Plan not found schema: $ref: '#/definitions/Error' 501: description: Resource not implemented schema: $ref: '#/definitions/Error' default: description: Unexpected error schema: $ref: '#/definitions/Error' definitions: Plan: type: object required: - name properties: name: type: string description: type: string info: type: object config: type: object Every ``operationId`` in above operations corresponds to a method in controllers. QTIP has three controller modules each for plan, QPI and metric. Connexion will read these mappings and automatically route endpoints to business logic. `Swagger Editor`_ can be explored to play with more such examples and to validate the specification. Controllers ----------- The request is handled through these methods and response is sent back to the client. Connexion takes care of data validation. .. code-block:: python @common.check_endpoint_for_error(resource='Plan') def get_plan(name): plan_spec = plan.Plan(name) return plan_spec.content In above code ``get_plan`` takes a plan name and return its content. The decorator ``check_endpoint_for_error`` defined in ``common`` is used to handle error and return a suitable error response. During Development the server can be run by passing specification file(``swagger.yaml`` in this case) to connexion cli - :: connexion run -v Extending the Framework ======================= Modifying Existing API: ----------------------- API can be modified by adding entries in ``swagger.yaml`` and adding the corresponding controller mapped from ``operationID``. Adding endpoints: New endpoints can be defined in ``paths`` section in ``swagger.yaml``. To add a new resource *dummy* - :: paths: /dummies: get: summary: Get all dummies operationId: qtip.api.controllers.dummy.get_dummies tags: - dummy responses: 200: description: Foo information schema: $ref: '#/definitions/Dummy default: description: Unexpected error schema: $ref: '#/definitions/Error' And then model of the resource can be defined in the ``definitions`` section. :: definitions: Dummy: type: object required: - name properties: name: type: string description: type: string id: type: string Adding controller methods: Methods for handling requests and responses for every operation for the endpoint added can be implemented in ``controller``. In ``controllers.dummy`` .. code-block:: python def get_dummies(): all_dummies = [] return all_dummies, httplib.OK Adding error responses Decorators for handling errors are defined in ``common.py`` in ``api``. .. code-block:: python from qtip.api import common @common.check_endpoint_for_error(resource='dummy',operation='get') def get_dummies() all_dummies = [] return all_dummies Adding new API: --------------- API can easily be extended by adding more APIs to ``Connexion.App`` class object using ``add_api`` class method. In ``__main__`` .. code-block:: python def get_app(): app = connexion.App(__name__, specification_dir=swagger_dir) app.add_api('swagger.yaml', base_path='/v1.0', strict_validation=True) return app Extending it to add new APIs. The new API should have all endpoints mapped using ``operationId``. .. code-block:: python from qtip.api import __main__ my_app = __main__.get_app() my_app.add_api('new_api.yaml',base_path'api2',strict_validation=True) my_app.run(host="0.0.0.0", port=5000) .. _Connexion: https://connexion.readthedocs.io/en/latest/ .. _Open API specification: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md .. _Swagger Editor: http://editor.swagger.io/