Revision 40ddc207

b/docs/developers/config.rst
111 111
* get(section, option): get the *value* of an *option* in the specified
112 112
    *section* e.g.,
113 113

  
114
    .. code-block:: python__
114
    .. code-block:: python
115 115

  
116 116
        # Example: get the default cloud (global.default_cloud option)
117 117

  
b/docs/examplesdir/server.rst
196 196
    adequate understanding of the remote OS are encouraged to prepare and
197 197
    inject all kinds of useful files, e.g., **lists of package sources**,
198 198
    **default user profiles**, **device mount configurations**, etc.
199

  
200
Clusters of virtual servers
201
---------------------------
202

  
203
A cluster of virtual servers can be created and deleted using special
204
arguments.
205

  
206
A convention is necessary: all servers belonging to the same cluster will have
207
names with a common prefix e.g., *cluster1*, *cluster2*, etc. This prefix
208
acts as the cluster name or the cluster key. Still, users must be careful not to
209
confuse cluster servers with other servers that coincidentally have the same
210
prefix (e.g., *cluster_of_stars*).
211

  
212
First, let's create a cluster of 4 servers. Each server will run the image with
213
id *f1r57-1m4g3-1d* on the hardware specified by the flavor with id *1*. The
214
prefix of the cluster will be "my cluster "
215

  
216
.. code-block:: console
217

  
218
    $ kamaki
219
    [kamaki]: server
220
    [server]: create "my cluster " 1 f1r57-1m4g3-1d --cluster-size=4 --wait
221
    ... <omitted for clarity>
222
    adminPass:       S0mePassw0rd0n3
223
    created:         2013-06-19T12:34:49.362078+00:00
224
    flavor:
225
            id:    1
226
    id:              322
227
    image:
228
            id:    f1r57-1m4g3-1d
229
    name:            my cluster 1
230
    [progress bar waiting server to build]
231
    Server 321: status is now ACTIVE
232

  
233
    ... <omitted for clarity>
234
    adminPass:       S0mePassw0rdTwo
235
    created:         2013-06-19T12:34:47.362078+00:00
236
    flavor:
237
            id:    1
238
    id:              321
239
    image:
240
            id:    f1r57-1m4g3-1d
241
    name:            my cluster 2
242
    [progress bar waiting server to build]
243
    Server 322: status is now ACTIVE
244

  
245
    ... <omitted for clarity>
246
    adminPass:       S0mePassw0rdThree
247
    created:         2013-06-19T12:34:55.362078+00:00
248
    flavor:
249
            id:    1
250
    id:              323
251
    image:
252
            id:    f1r57-1m4g3-1d
253
    name:            my cluster 3
254
    [progress bar waiting server to build]
255
    Server 323: status is now ACTIVE
256

  
257
    ... <omitted for clarity>
258
    adminPass:       S0mePassw0rdFour
259
    created:         2013-06-19T12:34:59.362078+00:00
260
    flavor:
261
            id:    1
262
    id:              324
263
    image:
264
            id:    f1r57-1m4g3-1d
265
    name:            my cluster 4
266
    [progress bar waiting server to build]
267
    Server 324: status is now ACTIVE
268

  
269
.. note:: The creation dates are similar but not ordered. This is because the
270
    servers are created asynchronously. To deactivate asynchronous operations
271
    in kamaki, set max_theads to 1
272

  
273
    .. code-block:: console
274

  
275
        # Deactivate multithreading
276

  
277
        [server]: /config set max_theads 1
278

  
279
.. note:: the *- - wait* argument is optional, but if not used, the *create*
280
    call will terminate as long as the servers are spawned, even if they are
281
    not built yet.
282

  
283
.. warning:: The server details (password, etc.) are printed in
284
    **standard output** while the progress bar and notification messages are
285
    printed in **standard error**
286

  
287
Now, let's see our clusters:
288

  
289
.. code-block:: console
290

  
291
    [server]: list --name-prefix "my cluster "
292
    321 my cluster 2
293
    322 my cluster 1
294
    323 my cluster 3
295
    324 my cluster 4
296

  
297
For demonstration purposes, let's suppose that the maximum resource limit is
298
reached if we create 2 more servers. We will attempt to expand "my cluster" by
299
4 servers, expecting kamaki to raise a quota-related error.
300

  
301
.. code-block:: console
302

  
303
    $ kamaki
304
    [kamaki]: server
305
    [server]: create "my cluster " 1 f1r57-1m4g3-1d --cluster-size=4 --wait
306
    Failed to build 4 servers
307
    Found 2 matching servers:
308
    325 my cluster 1
309
    326 my cluster 2
310
    Check if any of these servers should be removed
311

  
312
    (413) REQUEST ENTITY TOO LARGE overLimit (Resource Limit Exceeded for your
313
    account.)
314
    |  Limit for resource 'Virtual Machine' exceeded for your account.
315
    Available: 0, Requested: 1
316

  
317
The cluster expansion has failed, but 2 of the attempted 4 servers are being
318
created right now. It's up to the users judgment to destroy them or keep them.
319

  
320
First, we need to list all servers:
321

  
322
.. code-block:: console
323

  
324
    [server] list --name-prefix="my cluster "
325
    321 my cluster 2
326
    322 my cluster 1
327
    323 my cluster 3
328
    324 my cluster 4
329
    325 my cluster 1
330
    326 my cluster 2
331

  
332
.. warning:: Kamaki will always create clusters by attaching an increment at
333
    the right of the prefix. The increments always start from 1.
334

  
335
Now, our cluster seems messed up. Let's destroy it and rebuilt it.
336

  
337
.. code-block:: console
338

  
339
    [server]: delete "my cluster " --cluster --wait
340
    [progress bar waiting server to be deleted]
341
    Server 321: status is now DELETED
342

  
343
    [progress bar waiting server to be deleted]
344
    Server 322: status is now DELETED
345

  
346
    [progress bar waiting server to be deleted]
347
    Server 323: status is now DELETED
348

  
349
    [progress bar waiting server to be deleted]
350
    Server 324: status is now DELETED
351

  
352
    [progress bar waiting server to be deleted]
353
    Server 325: status is now DELETED
354

  
355
    [progress bar waiting server to be deleted]
356
    Server 326: status is now DELETED
357

  
358
.. note:: *delete* performs a single deletion if feeded with a server id, but
359
    it performs a mass deletion, based on the name, if called with --cluster
360

  
361
While creating the first cluster, we had to note down all passwords 
362

  
363
The passwords for each server are printed on the console while creating them.
364
It would be far more convenient, though, if we could massively inject an ssh
365
key into all of them. Let's do that!
366

  
367
.. code-block:: console
368

  
369
    [server]: create "my new cluster " 1 f1r57-1m4g3-1d --cluster-size=4 --wait --personality /home/someuser/.ssh/id_rsa.pub,/root/.ssh/authorized_keys,root,root,0777
370

  
371
    ... <output omitted for clarity>
372

  
373
Now, let's check if the cluster has been created.
374

  
375
.. code-block:: console
376

  
377
    [server]: list --name-prefix="my new cluster "
378
    321 my new cluster 1
379
    322 my new cluster 2
380
    323 my new cluster 3
381
    324 my new cluster 4
382

  
383
We now have a cluster of 4 virtual servers and we can ssh in all of them
384
without a password.
385

  
386
Here is a bash script for creating clusters:
387

  
388
.. code-block:: bash
389

  
390
    #!/bin/bash
391

  
392
    CL_PREFIX="cluster"
393
    CL_SIZE=4
394

  
395
    PUB_KEYS="${HOME}/.ssh/id_rsa.pub"
396
    OUT="cl_servers.txt"
397

  
398
    CLOUD=`kamaki config get default_cloud`
399
    FLAVOR_ID=1
400
    IMAGE_ID="f1r57-1m4g3-1d"
401

  
402
    echo "Clean up cluster \"${CL_PREFIX}\""
403
    kamaki --cloud=${CLOUD} server delete ${CL_PREFIX} --cluster --wait
404
    echo "Cluster \"${CL_PREFIX}\"" > ${OUT}
405

  
406
    echo "Create cluster \"${CL_PREFIX}\" of size ${CL_SIZE}"
407
    kamaki --cloud=${CLOUD} server create ${CL_PREFIX} ${FLAVOR_ID} ${IMAGE_ID}
408
        --cluster-size=${CL_SIZE} --wait
409
        --personality ${PUB_KEYS},/root/.ssh/authorized_keys,root,root >>${OUT}
410

  
411
    echo "A list of created servers can be found at ${OUT}"
b/kamaki/cli/commands/cyclades.py
389 389
            name='%s%s' % (prefix, i),
390 390
            flavor_id=flavor_id,
391 391
            image_id=image_id,
392
            personality=self['personality']) for i in range(size)]
392
            personality=self['personality']) for i in range(1, 1 + size)]
393 393
        if size == 1:
394 394
            return [self.client.create_server(**servers[0])]
395 395
        try:
396
            return self.client.async_run(self.client.create_server, servers)
396
            r = self.client.async_run(self.client.create_server, servers)
397
            return r
397 398
        except Exception as e:
398 399
            if size == 1:
399 400
                raise e
......
404 405
                    id=s['id']) for s in self.client.list_servers() if (
405 406
                        s['name'] in requested_names)]
406 407
                self.error('Failed to build %s servers' % size)
407
                self.error('Found %s servers with a "%s" prefix:' % (
408
                    len(spawned_servers), prefix))
408
                self.error('Found %s matching servers:' % len(spawned_servers))
409 409
                self._print(spawned_servers, out=self._err)
410 410
                self.error('Check if any of these servers should be removed\n')
411 411
            except Exception as ne:
......
430 430
            self._print(r, self.print_dict)
431 431
            if self['wait']:
432 432
                self._wait(r['id'], r['status'])
433
            self.writeln('')
433
            self.writeln(' ')
434 434

  
435 435
    def main(self, name, flavor_id, image_id):
436 436
        super(self.__class__, self)._run()
b/kamaki/clients/__init__.py
401 401
                thread.join()
402 402
            if thread.exception:
403 403
                raise thread.exception
404
            results[key] = thread.value
404 405
        return results.values()
405 406

  
406 407
    def _raise_for_status(self, r):

Also available in: Unified diff