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