1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
**********************************************
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 <path_to_specification_file> -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 = [<code to get 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 = [<code to get 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/
|