root / docs / developers / clients-api.rst @ 16d7b9ff
History | View | Annotate | Download (11.9 kB)
1 |
Creating applications with kamaki API |
---|---|
2 |
===================================== |
3 |
|
4 |
Kamaki features a clients API for building third-party client applications that |
5 |
communicate with OpenStack and / or Synnefo cloud services. The package is |
6 |
called *kamaki.clients* and serves as a lib. |
7 |
|
8 |
A showcase of an application built on *kamaki.clients* is *kamaki.cli*, the |
9 |
command line interface of kamaki. |
10 |
|
11 |
Since Synnefo services are build as OpenStack extensions, an inheritance |
12 |
approach has been chosen for implementing clients for both. In specific, |
13 |
the *compute*, *storage* and *image* modules are client implementations for the |
14 |
OpenStack compute and OpenStack object-store APIs, respectively. The rest of the |
15 |
modules implement the Synnefo extensions (i.e., *cyclades* and |
16 |
*cyclades_rest_api* extents *compute*, *pithos* and *pithos_rest_api* extent |
17 |
*storage*) or novel Synnefo services (*image* for *plankton*). |
18 |
|
19 |
Setup a client instance |
20 |
----------------------- |
21 |
|
22 |
There is a client for every API, therefore an external applications should |
23 |
instantiate they kamaki clients they need. For example, to manage virtual |
24 |
servers and stored objects / files, an application would probably need to |
25 |
instantiate the CycladesClient and PithosClient respectively. |
26 |
|
27 |
.. code-block:: python |
28 |
:emphasize-lines: 1 |
29 |
|
30 |
Example 1.1: Instantiate Cyclades and Pithos clients |
31 |
|
32 |
|
33 |
from kamaki.clients.cyclades import CycladesClient |
34 |
from kamaki.clients.pithos import PithosClient |
35 |
|
36 |
my_cyclades_client = CycladesClient(base_url, token) |
37 |
my_pithos_client = PithosClient(base_url, token, account, container) |
38 |
|
39 |
.. note:: *cyclades* and *pithos* clients inherit ComputeClient from *compute* |
40 |
and StorageClient from *storage*, respectively. Separate ComputeClient or |
41 |
StorageClient objects should be used only when implementing applications for |
42 |
strict OpenStack Compute or Storage services. |
43 |
|
44 |
Using endpoints to get the base_url |
45 |
----------------------------------- |
46 |
|
47 |
In OpenStack, each service (e.g., `compute`, `object-store`, etc.) has a number |
48 |
of `endpoints`. These `endpoints` are actually URIs that are used by kamaki as |
49 |
prefixes to form the corresponding API calls. Client applications need just |
50 |
one of these these `endpoints`, namely the `publicURL`, which is also referred |
51 |
to as `base_url` in kamaki client libraries. |
52 |
|
53 |
Here are instructions for getting the base_url for a service:: |
54 |
|
55 |
1. From the deployment UI get the AUTHENTICATION_URL and TOKEN |
56 |
(Example 1.2) |
57 |
2. Use them to instantiate an AstakosClient |
58 |
(Example 1.2) |
59 |
3. Use AstakosClient instance to get the endpoints of the service of interest |
60 |
(Example 1.3) |
61 |
4. The 'publicURL' endpoint is the base_url we are looking for |
62 |
(Example 1.3) |
63 |
|
64 |
The AstakosClient is a client for the Synnefo/Astakos server. Synnefo/Astakos |
65 |
is an identity server that implements the OpenStack identity API. Therefore, it |
66 |
can be used to get the `base_url` values needed for initializing kamaki clients. |
67 |
Kamaki simplifies this process with the astakos client library. |
68 |
|
69 |
Let's review the process with examples. |
70 |
|
71 |
First, an astakos client must be initialized (Example 1.2). An |
72 |
AUTHENTICATION_URL and a TOKEN can be acquired from the Synnefo deployment UI. |
73 |
|
74 |
.. code-block:: python |
75 |
:emphasize-lines: 1 |
76 |
|
77 |
Example 1.2: Initialize an astakos client |
78 |
|
79 |
from kamaki.clients.astakos import AstakosClient |
80 |
my_astakos_client = AstakosClient(AUTHENTICATION_URL, TOKEN) |
81 |
|
82 |
|
83 |
Next, the astakos client can be used to retrieve the base_url values for the |
84 |
servers of interest. In this case (Example 1.3) they are *cyclades* |
85 |
and *pithos*. A number of endpoints is assigned to each service, but kamaki |
86 |
clients only need the one labeled as ``publicURL``. |
87 |
|
88 |
.. code-block:: python |
89 |
:emphasize-lines: 1 |
90 |
|
91 |
Example 1.3: Retrieve cyclades and pithos base_url values |
92 |
|
93 |
cyclades_endpoints = my_astakos_client.get_service_endpoints('compute') |
94 |
cyclades_base_url = cyclades_endpoints['publicURL'] |
95 |
|
96 |
pithos_endpoints = my_astakos_client.get_service_endpoints('object-store') |
97 |
pithos_base_url = pithos_endpoints['publicURL'] |
98 |
|
99 |
The ``get_service_endpoints`` method is called with the service name as an |
100 |
argument. Here are the service names for the kamaki clients:: |
101 |
|
102 |
storage.StorageClient, pithos.PithosClient --> object-store |
103 |
compute.ComputeClient, cyclades.CycladesClient --> compute |
104 |
image.ImageClient --> image |
105 |
astakos.AstakosClient --> identity, account |
106 |
|
107 |
Use client methods |
108 |
------------------ |
109 |
|
110 |
At this point we assume that we can initialize a client, so the initialization |
111 |
step will be omitted in most of the examples that follow. |
112 |
|
113 |
The next step is to take a look at the member methods of each particular client. |
114 |
A detailed catalog of the member methods for all client classes can be found at |
115 |
:ref:`the-client-api-ref` |
116 |
|
117 |
In the following example, the *cyclades* and *pithos* clients of example 1.1 |
118 |
are used to extract some information through the remote service APIs. The information is then printed to the standard output. |
119 |
|
120 |
|
121 |
.. code-block:: python |
122 |
:emphasize-lines: 1,2 |
123 |
|
124 |
Example 1.4: Print server name and OS for server with server_id |
125 |
Print objects in container mycont |
126 |
|
127 |
srv = my_cyclades_client.get_server_info(server_id) |
128 |
print("Server Name: %s (with OS %s" % (srv['name'], srv['os'])) |
129 |
|
130 |
obj_list = my_pithos_client.list_objects(mycont) |
131 |
for obj in obj_list: |
132 |
print(' %s of %s bytes' % (obj['name'], obj['bytes'])) |
133 |
|
134 |
.. code-block:: console |
135 |
:emphasize-lines: 1 |
136 |
|
137 |
* A run of examples 1.1 + 1.4 * |
138 |
|
139 |
|
140 |
$ python test_script.py |
141 |
Server Name: A Debian Server (with OS Debian Base) |
142 |
lala.txt of 34 bytes |
143 |
test.txt of 1232 bytes |
144 |
testDir/ of 0 bytes |
145 |
$ |
146 |
|
147 |
Error handling |
148 |
-------------- |
149 |
|
150 |
The *kamaki.clients* error class is ClientError. A ClientError is raised for |
151 |
any kind of *kamaki.clients* errors (errors reported by servers, type errors in |
152 |
arguments, etc.). |
153 |
|
154 |
A ClientError contains:: |
155 |
|
156 |
message The error message. |
157 |
status An optional error code, e.g., after a server error. |
158 |
details Optional list of messages with error details. |
159 |
|
160 |
The following example concatenates examples 1.1 to 1.4 plus error handling |
161 |
|
162 |
.. code-block:: python |
163 |
|
164 |
Example 1.5: Error handling |
165 |
|
166 |
from kamaki.clients import ClientError |
167 |
|
168 |
from kamaki.clients.astakos import AstakosClient |
169 |
from kamaki.clients.cyclades import CycladesClient |
170 |
from kamaki.clients.pithos import PithosClient |
171 |
|
172 |
try: |
173 |
my_astakos_client = AstakosClient(AUTHENTICATION_URL, TOKEN) |
174 |
my_astakos_client.authenticate() |
175 |
except ClientError: |
176 |
print('Failed to authenticate user token') |
177 |
return 1 |
178 |
|
179 |
try: |
180 |
cyclades_endpoints = my_astakos_client.get_service_endpoints('compute') |
181 |
cyclades_base_url = cyclades_endpoints['publicURL'] |
182 |
except ClientError: |
183 |
print('Failed to get endpoints for cyclades') |
184 |
|
185 |
try: |
186 |
my_cyclades_client = CycladesClient(cyclades_base_url, token) |
187 |
except ClientError: |
188 |
print('Failed to initialize Cyclades client') |
189 |
|
190 |
try: |
191 |
pithos_endpoints = my_astakos_client.get_service_endpoints('object-store') |
192 |
pithos_base_url = pithos_endpoints['publicURL'] |
193 |
except ClientError: |
194 |
print('Failed to get endpoints for pithos') |
195 |
|
196 |
try: |
197 |
my_pithos_client = PithosClient(pithos_base_url, token, account, container) |
198 |
except ClientError: |
199 |
print('Failed to initialize Pithos+ client') |
200 |
|
201 |
try: |
202 |
srv = my_cyclades_client.get_server_info(server_id) |
203 |
print("Server Name: %s (with OS %s" % (srv['name'], srv['os'])) |
204 |
|
205 |
obj_list = my_pithos_client.list_objects(mycont) |
206 |
for obj in obj_list: |
207 |
print(' %s of %s bytes' % (obj['name'], obj['bytes'])) |
208 |
except ClientError as e: |
209 |
print('Error: %s' % e) |
210 |
if e.status: |
211 |
print('- error code: %s' % e.status) |
212 |
if e.details: |
213 |
for detail in e.details: |
214 |
print('- %s' % detail) |
215 |
|
216 |
|
217 |
Scripts |
218 |
------- |
219 |
|
220 |
Batch-create servers |
221 |
'''''''''''''''''''' |
222 |
|
223 |
.. code-block:: python |
224 |
|
225 |
#! /usr/bin/python |
226 |
|
227 |
from kamaki.clients.astakos import AstakosClient |
228 |
from kamaki.clients.cyclades import CycladesClient |
229 |
|
230 |
AUTHENTICATION_URL = 'https://accounts.example.com/identity/v2.0' |
231 |
TOKEN = 'replace this with your token' |
232 |
|
233 |
user = AstakosClient(AUTHENTICATION_URL, TOKEN) |
234 |
|
235 |
cyclades_endpoints = user.get_service_endpoints('compute') |
236 |
CYCLADES_URL = cyclades_endpoints['publicURL'] |
237 |
cyclades = CycladesClient(CYCLADES_URL, TOKEN) |
238 |
|
239 |
# (name, flavor-id, image-id) |
240 |
servers = [ |
241 |
('My Debian Server', 1, 'my-debian-base-image-id'), |
242 |
('My Windows Server', 3, 'my-windows-8-image-id'), |
243 |
('My Ubuntu Server', 3, 'my-ubuntu-12-image-id'), |
244 |
] |
245 |
|
246 |
for name, flavor_id, image_id in servers: |
247 |
cyclades.create_server(name, flavor_id, image_id) |
248 |
|
249 |
|
250 |
Batch-create 4 servers of the same kind |
251 |
''''''''''''''''''''''''''''''''''''''' |
252 |
|
253 |
.. code-block:: python |
254 |
|
255 |
#! /usr/bin/python |
256 |
|
257 |
from kamaki.clients.astakos import AstakosClient |
258 |
from kamaki.clients.cyclades import CycladesClient |
259 |
|
260 |
AUTHENTICATION_URL = 'https://accounts.example.com/identity/v2.0' |
261 |
TOKEN = 'replace this with your token' |
262 |
|
263 |
user = AstakosClient(AUTHENTICATION_URL, TOKEN) |
264 |
|
265 |
cyclades_endpoints = user.get_service_endpoints('compute') |
266 |
CYCLADES_URL = cyclades_endpoints['publicURL'] |
267 |
cyclades = CycladesClient(CYCLADES_URL, TOKEN) |
268 |
|
269 |
for i in range(4): |
270 |
name, flavor_id, image_id = 'Server %s' % (i + 1), 3, 'some-image-id' |
271 |
cyclades.create_server(name, flavor_id, image_id) |
272 |
|
273 |
Register a banch of pre-uploaded images |
274 |
''''''''''''''''''''''''''''''''''''''' |
275 |
|
276 |
.. code-block:: python |
277 |
|
278 |
#! /usr/bin/python |
279 |
|
280 |
from kamaki.clients import ClientError |
281 |
from kamaki.clients.astakos import AstakosClient |
282 |
from kamaki.clients.pithos import PithosClient |
283 |
from kamaki.clients.image import ImageClient |
284 |
|
285 |
AUTHENTICATION_URL = 'https://accounts.example.com/identity/v2.0' |
286 |
TOKEN = 'replace this with your token' |
287 |
IMAGE_CONTAINER = 'images' |
288 |
|
289 |
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN) |
290 |
USER_UUID = astakos.user_term('uuid') |
291 |
|
292 |
PITHOS_URL = astakos.get_service_endpoints('object-store')['publicURL'] |
293 |
pithos = PithosClient(PITHOS_URL, TOKEN, USER_UUID, IMAGE_CONTAINER) |
294 |
|
295 |
IMAGE_URL = astakos.get_service_endpoints('image')['publicURL'] |
296 |
plankton = ImageClient(IMAGE_URL, TOKEN) |
297 |
|
298 |
for img in pithos.list_objects(): |
299 |
IMAGE_PATH = img['name'] |
300 |
try: |
301 |
r = plankton.register( |
302 |
name='Image %s' % img, |
303 |
location=(USER_UUID, IMAGE_CONTAINER, IMAGE_PATH)) |
304 |
print 'Image %s registered with id %s' % (r['name'], r['id']) |
305 |
except ClientError: |
306 |
print 'Failed to register image %s' % IMAGE_PATH |
307 |
|
308 |
Two servers and a private network |
309 |
''''''''''''''''''''''''''''''''' |
310 |
|
311 |
.. code-block:: python |
312 |
|
313 |
#! /user/bin/python |
314 |
|
315 |
from kamaki.clients.astakos import AstakosClient |
316 |
from kamaki.clients.cyclades import CycladesClient |
317 |
|
318 |
AUTHENTICATION_URL = 'https://accounts.example.com/identity/v2.0' |
319 |
TOKEN = 'replace this with your token' |
320 |
|
321 |
astakos = AstakosClient(AUTHENTICATION_URL, TOKEN) |
322 |
|
323 |
cyclades_endpoints = user.get_service_endpoints('compute') |
324 |
CYCLADES_URL = cyclades_endpoints['publicURL'] |
325 |
|
326 |
FLAVOR_ID = 'put your flavor id here' |
327 |
IMAGE_ID = 'put your image id here' |
328 |
|
329 |
cyclades = CycladesClient(CYCLADES_URL, TOKEN) |
330 |
|
331 |
srv1 = cyclades.create_server('server 1', FLAVOR_ID, IMAGE_ID) |
332 |
srv2 = cyclades.create_server('server 2', FLAVOR_ID, IMAGE_ID) |
333 |
|
334 |
srv_state1 = cyclades.wait_server(srv1['id']) |
335 |
assert srv_state1 in ('ACTIVE'), 'Server 1 built failure' |
336 |
|
337 |
srv_state2 = cyclades.wait_server(srv2['id']) |
338 |
assert srv_state2 in ('ACTIVE'), 'Server 2 built failure' |
339 |
|
340 |
net = cyclades.create_network('My private network') |
341 |
net_state = cyclades.wait_network(net['id']) |
342 |
assert net_state in ('ACTIVE', ), 'Network built failure' |
343 |
|
344 |
cyclades.connect_server(srv1['id'], net['id']) |
345 |
cyclades.connect_server(srv2['id'], net['id']) |