Statistics
| Branch: | Tag: | Revision:

root / doc / rapi.rst @ 0565f862

History | View | Annotate | Download (50 kB)

1
Ganeti remote API
2
=================
3

    
4
Documents Ganeti version |version|
5

    
6
.. contents::
7

    
8
Introduction
9
------------
10

    
11
Ganeti supports a remote API for enable external tools to easily
12
retrieve information about a cluster's state. The remote API daemon,
13
*ganeti-rapi*, is automatically started on the master node. By default
14
it runs on TCP port 5080, but this can be changed either in
15
``.../constants.py`` or via the command line parameter *-p*. SSL mode,
16
which is used by default, can also be disabled by passing command line
17
parameters.
18

    
19
.. _rapi-users:
20

    
21
Users and passwords
22
-------------------
23

    
24
``ganeti-rapi`` reads users and passwords from a file (usually
25
``/var/lib/ganeti/rapi/users``) on startup. Changes to the file will be
26
read automatically.
27

    
28
Lines starting with the hash sign (``#``) are treated as comments. Each
29
line consists of two or three fields separated by whitespace. The first
30
two fields are for username and password. The third field is optional
31
and can be used to specify per-user options (separated by comma without
32
spaces).
33

    
34
Passwords can either be written in clear text or as a hash. Clear text
35
passwords may not start with an opening brace (``{``) or they must be
36
prefixed with ``{cleartext}``. To use the hashed form, get the MD5 hash
37
of the string ``$username:Ganeti Remote API:$password`` (e.g. ``echo -n
38
'jack:Ganeti Remote API:abc123' | openssl md5``) [#pwhash]_ and prefix
39
it with ``{ha1}``. Using the scheme prefix for all passwords is
40
recommended. Scheme prefixes are case insensitive.
41

    
42
Options control a user's access permissions. The section
43
:ref:`rapi-access-permissions` lists the permissions required for each
44
resource. If the ``--require-authentication`` command line option is
45
given to the ``ganeti-rapi`` daemon, all requests require
46
authentication. Available options:
47

    
48
.. pyassert::
49

    
50
  rapi.RAPI_ACCESS_ALL == set([
51
    rapi.RAPI_ACCESS_WRITE,
52
    rapi.RAPI_ACCESS_READ,
53
    ])
54

    
55
.. pyassert::
56

    
57
  rlib2.R_2_nodes_name_storage.GET_ACCESS == [rapi.RAPI_ACCESS_WRITE]
58

    
59
.. pyassert::
60

    
61
  rlib2.R_2_jobs_id_wait.GET_ACCESS == [rapi.RAPI_ACCESS_WRITE]
62

    
63
:pyeval:`rapi.RAPI_ACCESS_WRITE`
64
  Enables the user to execute operations modifying the cluster. Implies
65
  :pyeval:`rapi.RAPI_ACCESS_READ` access. Resources blocking other
66
  operations for read-only access, such as
67
  :ref:`/2/nodes/[node_name]/storage <rapi-res-nodes-node_name-storage+get>`
68
  or blocking server-side processes, such as
69
  :ref:`/2/jobs/[job_id]/wait <rapi-res-jobs-job_id-wait+get>`, use
70
  :pyeval:`rapi.RAPI_ACCESS_WRITE` to control access to their
71
  :pyeval:`http.HTTP_GET` method.
72
:pyeval:`rapi.RAPI_ACCESS_READ`
73
  Allow access to operations querying for information.
74

    
75
Example::
76

    
77
  # Give Jack and Fred read-only access
78
  jack abc123
79
  fred {cleartext}foo555
80

    
81
  # Give write access to an imaginary instance creation script
82
  autocreator xyz789 write
83

    
84
  # Hashed password for Jessica
85
  jessica {HA1}7046452df2cbb530877058712cf17bd4 write
86

    
87
  # Monitoring can query for values
88
  monitoring {HA1}ec018ffe72b8e75bb4d508ed5b6d079c read
89

    
90
  # A user who can read and write (the former is implied by granting
91
  # write access)
92
  superuser {HA1}ec018ffe72b8e75bb4d508ed5b6d079c read,write
93

    
94
When using the RAPI, username and password can be sent to the server
95
by using the standard HTTP basic access authentication. This means that
96
for accessing the protected URL ``https://cluster.example.com/resource``,
97
the address ``https://username:password@cluster.example.com/resource`` should
98
be used instead.
99
Alternatively, the appropriate parameter of your HTTP client
100
(such as ``-u`` for ``curl``) can be used.
101

    
102
.. [#pwhash] Using the MD5 hash of username, realm and password is
103
   described in :rfc:`2617` ("HTTP Authentication"), sections 3.2.2.2
104
   and 3.3. The reason for using it over another algorithm is forward
105
   compatibility. If ``ganeti-rapi`` were to implement HTTP Digest
106
   authentication in the future, the same hash could be used.
107
   In the current version ``ganeti-rapi``'s realm, ``Ganeti Remote
108
   API``, can only be changed by modifying the source code.
109

    
110

    
111
Protocol
112
--------
113

    
114
The protocol used is JSON_ over HTTP designed after the REST_ principle.
115
HTTP Basic authentication as per :rfc:`2617` is supported.
116

    
117
.. _JSON: http://www.json.org/
118
.. _REST: http://en.wikipedia.org/wiki/Representational_State_Transfer
119

    
120
HTTP requests with a body (e.g. ``PUT`` or ``POST``) require the request
121
header ``Content-type`` be set to ``application/json`` (see :rfc:`2616`
122
(HTTP/1.1), section 7.2.1).
123

    
124

    
125
A note on JSON as used by RAPI
126
++++++++++++++++++++++++++++++
127

    
128
JSON_ as used by Ganeti RAPI does not conform to the specification in
129
:rfc:`4627`. Section 2 defines a JSON text to be either an object
130
(``{"key": "value", …}``) or an array (``[1, 2, 3, …]``). In violation
131
of this RAPI uses plain strings (``"master-candidate"``, ``"1234"``) for
132
some requests or responses. Changing this now would likely break
133
existing clients and cause a lot of trouble.
134

    
135
.. highlight:: ruby
136

    
137
Unlike Python's `JSON encoder and decoder
138
<http://docs.python.org/library/json.html>`_, other programming
139
languages or libraries may only provide a strict implementation, not
140
allowing plain values. For those, responses can usually be wrapped in an
141
array whose first element is then used, e.g. the response ``"1234"``
142
becomes ``["1234"]``. This works equally well for more complex values.
143
Example in Ruby::
144

    
145
  require "json"
146

    
147
  # Insert code to get response here
148
  response = "\"1234\""
149

    
150
  decoded = JSON.parse("[#{response}]").first
151

    
152
Short of modifying the encoder to allow encoding to a less strict
153
format, requests will have to be formatted by hand. Newer RAPI requests
154
already use a dictionary as their input data and shouldn't cause any
155
problems.
156

    
157

    
158
PUT or POST?
159
------------
160

    
161
According to :rfc:`2616` the main difference between PUT and POST is
162
that POST can create new resources but PUT can only create the resource
163
the URI was pointing to on the PUT request.
164

    
165
Unfortunately, due to historic reasons, the Ganeti RAPI library is not
166
consistent with this usage, so just use the methods as documented below
167
for each resource.
168

    
169
For more details have a look in the source code at
170
``lib/rapi/rlib2.py``.
171

    
172

    
173
Generic parameter types
174
-----------------------
175

    
176
A few generic refered parameter types and the values they allow.
177

    
178
``bool``
179
++++++++
180

    
181
A boolean option will accept ``1`` or ``0`` as numbers but not
182
i.e. ``True`` or ``False``.
183

    
184
Generic parameters
185
------------------
186

    
187
A few parameter mean the same thing across all resources which implement
188
it.
189

    
190
``bulk``
191
++++++++
192

    
193
Bulk-mode means that for the resources which usually return just a list
194
of child resources (e.g. ``/2/instances`` which returns just instance
195
names), the output will instead contain detailed data for all these
196
subresources. This is more efficient than query-ing the sub-resources
197
themselves.
198

    
199
``dry-run``
200
+++++++++++
201

    
202
The boolean *dry-run* argument, if provided and set, signals to Ganeti
203
that the job should not be executed, only the pre-execution checks will
204
be done.
205

    
206
This is useful in trying to determine (without guarantees though, as in
207
the meantime the cluster state could have changed) if the operation is
208
likely to succeed or at least start executing.
209

    
210
``force``
211
+++++++++++
212

    
213
Force operation to continue even if it will cause the cluster to become
214
inconsistent (e.g. because there are not enough master candidates).
215

    
216
Parameter details
217
-----------------
218

    
219
Some parameters are not straight forward, so we describe them in details
220
here.
221

    
222
.. _rapi-ipolicy:
223

    
224
``ipolicy``
225
+++++++++++
226

    
227
The instance policy specification is a dict with the following fields:
228

    
229
.. pyassert::
230

    
231
  constants.IPOLICY_ALL_KEYS == set([constants.ISPECS_MINMAX,
232
                                     constants.ISPECS_STD,
233
                                     constants.IPOLICY_DTS,
234
                                     constants.IPOLICY_VCPU_RATIO,
235
                                     constants.IPOLICY_SPINDLE_RATIO])
236

    
237

    
238
.. pyassert::
239

    
240
  (set(constants.ISPECS_PARAMETER_TYPES.keys()) ==
241
   set([constants.ISPEC_MEM_SIZE,
242
        constants.ISPEC_DISK_SIZE,
243
        constants.ISPEC_DISK_COUNT,
244
        constants.ISPEC_CPU_COUNT,
245
        constants.ISPEC_NIC_COUNT,
246
        constants.ISPEC_SPINDLE_USE]))
247

    
248
.. |ispec-min| replace:: :pyeval:`constants.ISPECS_MIN`
249
.. |ispec-max| replace:: :pyeval:`constants.ISPECS_MAX`
250
.. |ispec-std| replace:: :pyeval:`constants.ISPECS_STD`
251

    
252

    
253
:pyeval:`constants.ISPECS_MINMAX`
254
  A list of dictionaries, each with the following two fields:
255

    
256
  |ispec-min|, |ispec-max|
257
    A sub- `dict` with the following fields, which sets the limit of the
258
    instances:
259

    
260
    :pyeval:`constants.ISPEC_MEM_SIZE`
261
      The size in MiB of the memory used
262
    :pyeval:`constants.ISPEC_DISK_SIZE`
263
      The size in MiB of the disk used
264
    :pyeval:`constants.ISPEC_DISK_COUNT`
265
      The numbers of disks used
266
    :pyeval:`constants.ISPEC_CPU_COUNT`
267
      The numbers of cpus used
268
    :pyeval:`constants.ISPEC_NIC_COUNT`
269
      The numbers of nics used
270
    :pyeval:`constants.ISPEC_SPINDLE_USE`
271
      The numbers of virtual disk spindles used by this instance. They
272
      are not real in the sense of actual HDD spindles, but useful for
273
      accounting the spindle usage on the residing node
274
|ispec-std|
275
  A sub- `dict` with the same fields as |ispec-min| and |ispec-max| above,
276
  which sets the standard values of the instances.
277
:pyeval:`constants.IPOLICY_DTS`
278
  A `list` of disk templates allowed for instances using this policy
279
:pyeval:`constants.IPOLICY_VCPU_RATIO`
280
  Maximum ratio of virtual to physical CPUs (`float`)
281
:pyeval:`constants.IPOLICY_SPINDLE_RATIO`
282
  Maximum ratio of instances to their node's ``spindle_count`` (`float`)
283

    
284
Usage examples
285
--------------
286

    
287
You can access the API using your favorite programming language as long
288
as it supports network connections.
289

    
290
Ganeti RAPI client
291
++++++++++++++++++
292

    
293
Ganeti includes a standalone RAPI client, ``lib/rapi/client.py``.
294

    
295
Shell
296
+++++
297

    
298
.. highlight:: shell-example
299

    
300
Using ``wget``::
301

    
302
   $ wget -q -O - https://%CLUSTERNAME%:5080/2/info
303

    
304
or ``curl``::
305

    
306
  $ curl https://%CLUSTERNAME%:5080/2/info
307

    
308
Note: with ``curl``, the request method (GET, POST, PUT) can be specified
309
using the ``-X`` command line option, and the username/password can be
310
specified with the ``-u`` option. In case of POST requests with a body, the
311
Content-Type can be set to JSON (as per the Protocol_ section) using the
312
parameter ``-H "Content-Type: application/json"``.
313

    
314
Python
315
++++++
316

    
317
.. highlight:: python
318

    
319
::
320

    
321
  import urllib2
322
  f = urllib2.urlopen('https://CLUSTERNAME:5080/2/info')
323
  print f.read()
324

    
325

    
326
JavaScript
327
++++++++++
328

    
329
.. warning:: While it's possible to use JavaScript, it poses several
330
   potential problems, including browser blocking request due to
331
   non-standard ports or different domain names. Fetching the data on
332
   the webserver is easier.
333

    
334
.. highlight:: javascript
335

    
336
::
337

    
338
  var url = 'https://CLUSTERNAME:5080/2/info';
339
  var info;
340
  var xmlreq = new XMLHttpRequest();
341
  xmlreq.onreadystatechange = function () {
342
    if (xmlreq.readyState != 4) return;
343
    if (xmlreq.status == 200) {
344
      info = eval("(" + xmlreq.responseText + ")");
345
      alert(info);
346
    } else {
347
      alert('Error fetching cluster info');
348
    }
349
    xmlreq = null;
350
  };
351
  xmlreq.open('GET', url, true);
352
  xmlreq.send(null);
353

    
354
Resources
355
---------
356

    
357
.. highlight:: javascript
358

    
359
``/``
360
+++++
361

    
362
The root resource. Has no function, but for legacy reasons the ``GET``
363
method is supported.
364

    
365
``/2``
366
++++++
367

    
368
Has no function, but for legacy reasons the ``GET`` method is supported.
369

    
370
.. _rapi-res-info:
371

    
372
``/2/info``
373
+++++++++++
374

    
375
Cluster information resource.
376

    
377
.. rapi_resource_details:: /2/info
378

    
379

    
380
.. _rapi-res-info+get:
381

    
382
``GET``
383
~~~~~~~
384

    
385
Returns cluster information.
386

    
387
Example::
388

    
389
  {
390
    "config_version": 2000000,
391
    "name": "cluster",
392
    "software_version": "2.0.0~beta2",
393
    "os_api_version": 10,
394
    "export_version": 0,
395
    "candidate_pool_size": 10,
396
    "enabled_hypervisors": [
397
      "fake"
398
    ],
399
    "hvparams": {
400
      "fake": {}
401
     },
402
    "default_hypervisor": "fake",
403
    "master": "node1.example.com",
404
    "architecture": [
405
      "64bit",
406
      "x86_64"
407
    ],
408
    "protocol_version": 20,
409
    "beparams": {
410
      "default": {
411
        "auto_balance": true,
412
        "vcpus": 1,
413
        "memory": 128
414
       }
415
      },
416
417
  }
418

    
419

    
420
.. _rapi-res-redistribute-config:
421

    
422
``/2/redistribute-config``
423
++++++++++++++++++++++++++
424

    
425
Redistribute configuration to all nodes.
426

    
427
.. rapi_resource_details:: /2/redistribute-config
428

    
429

    
430
.. _rapi-res-redistribute-config+put:
431

    
432
``PUT``
433
~~~~~~~
434

    
435
Redistribute configuration to all nodes. The result will be a job id.
436

    
437
Job result:
438

    
439
.. opcode_result:: OP_CLUSTER_REDIST_CONF
440

    
441

    
442
.. _rapi-res-features:
443

    
444
``/2/features``
445
+++++++++++++++
446

    
447
.. rapi_resource_details:: /2/features
448

    
449

    
450
.. _rapi-res-features+get:
451

    
452
``GET``
453
~~~~~~~
454

    
455
Returns a list of features supported by the RAPI server. Available
456
features:
457

    
458
.. pyassert::
459

    
460
  rlib2.ALL_FEATURES == set([rlib2._INST_CREATE_REQV1,
461
                             rlib2._INST_REINSTALL_REQV1,
462
                             rlib2._NODE_MIGRATE_REQV1,
463
                             rlib2._NODE_EVAC_RES1])
464

    
465
:pyeval:`rlib2._INST_CREATE_REQV1`
466
  Instance creation request data version 1 supported
467
:pyeval:`rlib2._INST_REINSTALL_REQV1`
468
  Instance reinstall supports body parameters
469
:pyeval:`rlib2._NODE_MIGRATE_REQV1`
470
  Whether migrating a node (``/2/nodes/[node_name]/migrate``) supports
471
  request body parameters
472
:pyeval:`rlib2._NODE_EVAC_RES1`
473
  Whether evacuating a node (``/2/nodes/[node_name]/evacuate``) returns
474
  a new-style result (see resource description)
475

    
476

    
477
.. _rapi-res-modify:
478

    
479
``/2/modify``
480
++++++++++++++++++++++++++++++++++++++++
481

    
482
Modifies cluster parameters.
483

    
484
.. rapi_resource_details:: /2/modify
485

    
486

    
487
.. _rapi-res-modify+put:
488

    
489
``PUT``
490
~~~~~~~
491

    
492
Returns a job ID.
493

    
494
Body parameters:
495

    
496
.. opcode_params:: OP_CLUSTER_SET_PARAMS
497

    
498
Job result:
499

    
500
.. opcode_result:: OP_CLUSTER_SET_PARAMS
501

    
502

    
503
.. _rapi-res-groups:
504

    
505
``/2/groups``
506
+++++++++++++
507

    
508
The groups resource.
509

    
510
.. rapi_resource_details:: /2/groups
511

    
512

    
513
.. _rapi-res-groups+get:
514

    
515
``GET``
516
~~~~~~~
517

    
518
Returns a list of all existing node groups.
519

    
520
Example::
521

    
522
    [
523
      {
524
        "name": "group1",
525
        "uri": "\/2\/groups\/group1"
526
      },
527
      {
528
        "name": "group2",
529
        "uri": "\/2\/groups\/group2"
530
      }
531
    ]
532

    
533
If the optional bool *bulk* argument is provided and set to a true value
534
(i.e ``?bulk=1``), the output contains detailed information about node
535
groups as a list.
536

    
537
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.G_FIELDS))`.
538

    
539
Example::
540

    
541
    [
542
      {
543
        "name": "group1",
544
        "node_cnt": 2,
545
        "node_list": [
546
          "node1.example.com",
547
          "node2.example.com"
548
        ],
549
        "uuid": "0d7d407c-262e-49af-881a-6a430034bf43",
550
551
      },
552
      {
553
        "name": "group2",
554
        "node_cnt": 1,
555
        "node_list": [
556
          "node3.example.com"
557
        ],
558
        "uuid": "f5a277e7-68f9-44d3-a378-4b25ecb5df5c",
559
560
      },
561
562
    ]
563

    
564

    
565
.. _rapi-res-groups+post:
566

    
567
``POST``
568
~~~~~~~~
569

    
570
Creates a node group.
571

    
572
If the optional bool *dry-run* argument is provided, the job will not be
573
actually executed, only the pre-execution checks will be done.
574

    
575
Returns: a job ID that can be used later for polling.
576

    
577
Body parameters:
578

    
579
.. opcode_params:: OP_GROUP_ADD
580

    
581
Earlier versions used a parameter named ``name`` which, while still
582
supported, has been renamed to ``group_name``.
583

    
584
Job result:
585

    
586
.. opcode_result:: OP_GROUP_ADD
587

    
588

    
589
.. _rapi-res-groups-group_name:
590

    
591
``/2/groups/[group_name]``
592
++++++++++++++++++++++++++
593

    
594
Returns information about a node group.
595

    
596
.. rapi_resource_details:: /2/groups/[group_name]
597

    
598

    
599
.. _rapi-res-groups-group_name+get:
600

    
601
``GET``
602
~~~~~~~
603

    
604
Returns information about a node group, similar to the bulk output from
605
the node group list.
606

    
607
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.G_FIELDS))`.
608

    
609
.. _rapi-res-groups-group_name+delete:
610

    
611
``DELETE``
612
~~~~~~~~~~
613

    
614
Deletes a node group.
615

    
616
It supports the ``dry-run`` argument.
617

    
618
Job result:
619

    
620
.. opcode_result:: OP_GROUP_REMOVE
621

    
622

    
623
.. _rapi-res-groups-group_name-modify:
624

    
625
``/2/groups/[group_name]/modify``
626
+++++++++++++++++++++++++++++++++
627

    
628
Modifies the parameters of a node group.
629

    
630
.. rapi_resource_details:: /2/groups/[group_name]/modify
631

    
632

    
633
.. _rapi-res-groups-group_name-modify+put:
634

    
635
``PUT``
636
~~~~~~~
637

    
638
Returns a job ID.
639

    
640
Body parameters:
641

    
642
.. opcode_params:: OP_GROUP_SET_PARAMS
643
   :exclude: group_name
644

    
645
Job result:
646

    
647
.. opcode_result:: OP_GROUP_SET_PARAMS
648

    
649

    
650
.. _rapi-res-groups-group_name-rename:
651

    
652
``/2/groups/[group_name]/rename``
653
+++++++++++++++++++++++++++++++++
654

    
655
Renames a node group.
656

    
657
.. rapi_resource_details:: /2/groups/[group_name]/rename
658

    
659

    
660
.. _rapi-res-groups-group_name-rename+put:
661

    
662
``PUT``
663
~~~~~~~
664

    
665
Returns a job ID.
666

    
667
Body parameters:
668

    
669
.. opcode_params:: OP_GROUP_RENAME
670
   :exclude: group_name
671

    
672
Job result:
673

    
674
.. opcode_result:: OP_GROUP_RENAME
675

    
676

    
677
.. _rapi-res-groups-group_name-assign-nodes:
678

    
679
``/2/groups/[group_name]/assign-nodes``
680
+++++++++++++++++++++++++++++++++++++++
681

    
682
Assigns nodes to a group.
683

    
684
.. rapi_resource_details:: /2/groups/[group_name]/assign-nodes
685

    
686
.. _rapi-res-groups-group_name-assign-nodes+put:
687

    
688
``PUT``
689
~~~~~~~
690

    
691
Returns a job ID. It supports the ``dry-run`` and ``force`` arguments.
692

    
693
Body parameters:
694

    
695
.. opcode_params:: OP_GROUP_ASSIGN_NODES
696
   :exclude: group_name, force, dry_run
697

    
698
Job result:
699

    
700
.. opcode_result:: OP_GROUP_ASSIGN_NODES
701

    
702
.. _rapi-res-groups-group_name-tags:
703

    
704
``/2/groups/[group_name]/tags``
705
+++++++++++++++++++++++++++++++
706

    
707
Manages per-nodegroup tags.
708

    
709
.. rapi_resource_details:: /2/groups/[group_name]/tags
710

    
711

    
712
.. _rapi-res-groups-group_name-tags+get:
713

    
714
``GET``
715
~~~~~~~
716

    
717
Returns a list of tags.
718

    
719
Example::
720

    
721
    ["tag1", "tag2", "tag3"]
722

    
723
.. _rapi-res-groups-group_name-tags+put:
724

    
725
``PUT``
726
~~~~~~~
727

    
728
Add a set of tags.
729

    
730
The request as a list of strings should be ``PUT`` to this URI. The
731
result will be a job id.
732

    
733
It supports the ``dry-run`` argument.
734

    
735

    
736
.. _rapi-res-groups-group_name-tags+delete:
737

    
738
``DELETE``
739
~~~~~~~~~~
740

    
741
Delete a tag.
742

    
743
In order to delete a set of tags, the DELETE request should be addressed
744
to URI like::
745

    
746
    /tags?tag=[tag]&tag=[tag]
747

    
748
It supports the ``dry-run`` argument.
749

    
750

    
751
.. _rapi-res-networks:
752

    
753
``/2/networks``
754
+++++++++++++++
755

    
756
The networks resource.
757

    
758
.. rapi_resource_details:: /2/networks
759

    
760

    
761
.. _rapi-res-networks+get:
762

    
763
``GET``
764
~~~~~~~
765

    
766
Returns a list of all existing networks.
767

    
768
Example::
769

    
770
    [
771
      {
772
        "name": "network1",
773
        "uri": "\/2\/networks\/network1"
774
      },
775
      {
776
        "name": "network2",
777
        "uri": "\/2\/networks\/network2"
778
      }
779
    ]
780

    
781
If the optional bool *bulk* argument is provided and set to a true value
782
(i.e ``?bulk=1``), the output contains detailed information about networks
783
as a list.
784

    
785
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.NET_FIELDS))`.
786

    
787
Example::
788

    
789
    [
790
      {
791
        'external_reservations': '10.0.0.0, 10.0.0.1, 10.0.0.15',
792
        'free_count': 13,
793
        'gateway': '10.0.0.1',
794
        'gateway6': None,
795
        'group_list': ['default(bridged, prv0)'],
796
        'inst_list': [],
797
        'mac_prefix': None,
798
        'map': 'XX.............X',
799
        'name': 'nat',
800
        'network': '10.0.0.0/28',
801
        'network6': None,
802
        'reserved_count': 3,
803
        'tags': ['nfdhcpd'],
804
805
      },
806
807
    ]
808

    
809

    
810
.. _rapi-res-networks+post:
811

    
812
``POST``
813
~~~~~~~~
814

    
815
Creates a network.
816

    
817
If the optional bool *dry-run* argument is provided, the job will not be
818
actually executed, only the pre-execution checks will be done.
819

    
820
Returns: a job ID that can be used later for polling.
821

    
822
Body parameters:
823

    
824
.. opcode_params:: OP_NETWORK_ADD
825

    
826
Job result:
827

    
828
.. opcode_result:: OP_NETWORK_ADD
829

    
830

    
831
.. _rapi-res-networks-network_name:
832

    
833
``/2/networks/[network_name]``
834
++++++++++++++++++++++++++++++
835

    
836
Returns information about a network.
837

    
838
.. rapi_resource_details:: /2/networks/[network_name]
839

    
840

    
841
.. _rapi-res-networks-network_name+get:
842

    
843
``GET``
844
~~~~~~~
845

    
846
Returns information about a network, similar to the bulk output from
847
the network list.
848

    
849
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.NET_FIELDS))`.
850

    
851

    
852
.. _rapi-res-networks-network_name+delete:
853

    
854
``DELETE``
855
~~~~~~~~~~
856

    
857
Deletes a network.
858

    
859
It supports the ``dry-run`` argument.
860

    
861
Job result:
862

    
863
.. opcode_result:: OP_NETWORK_REMOVE
864

    
865

    
866
.. _rapi-res-networks-network_name-modify:
867

    
868
``/2/networks/[network_name]/modify``
869
+++++++++++++++++++++++++++++++++++++
870

    
871
Modifies the parameters of a network.
872

    
873
.. rapi_resource_details:: /2/networks/[network_name]/modify
874

    
875

    
876
.. _rapi-res-networks-network_name-modify+put:
877

    
878
``PUT``
879
~~~~~~~
880

    
881
Returns a job ID.
882

    
883
Body parameters:
884

    
885
.. opcode_params:: OP_NETWORK_SET_PARAMS
886

    
887
Job result:
888

    
889
.. opcode_result:: OP_NETWORK_SET_PARAMS
890

    
891

    
892
.. _rapi-res-networks-network_name-connect:
893

    
894
``/2/networks/[network_name]/connect``
895
++++++++++++++++++++++++++++++++++++++
896

    
897
Connects a network to a nodegroup.
898

    
899
.. rapi_resource_details:: /2/networks/[network_name]/connect
900

    
901

    
902
.. _rapi-res-networks-network_name-connect+put:
903

    
904
``PUT``
905
~~~~~~~
906

    
907
Returns a job ID. It supports the ``dry-run`` arguments.
908

    
909
Body parameters:
910

    
911
.. opcode_params:: OP_NETWORK_CONNECT
912

    
913
Job result:
914

    
915
.. opcode_result:: OP_NETWORK_CONNECT
916

    
917

    
918
.. _rapi-res-networks-network_name-disconnect:
919

    
920
``/2/networks/[network_name]/disconnect``
921
+++++++++++++++++++++++++++++++++++++++++
922

    
923
Disonnects a network from a nodegroup.
924

    
925
.. rapi_resource_details:: /2/networks/[network_name]/disconnect
926

    
927

    
928
.. _rapi-res-networks-network_name-disconnect+put:
929

    
930
``PUT``
931
~~~~~~~
932

    
933
Returns a job ID. It supports the ``dry-run`` arguments.
934

    
935
Body parameters:
936

    
937
.. opcode_params:: OP_NETWORK_DISCONNECT
938

    
939
Job result:
940

    
941
.. opcode_result:: OP_NETWORK_DISCONNECT
942

    
943

    
944
.. _rapi-res-networks-network_name-tags:
945

    
946
``/2/networks/[network_name]/tags``
947
+++++++++++++++++++++++++++++++++++
948

    
949
Manages per-network tags.
950

    
951
.. rapi_resource_details:: /2/networks/[network_name]/tags
952

    
953

    
954
.. _rapi-res-networks-network_name-tags+get:
955

    
956
``GET``
957
~~~~~~~
958

    
959
Returns a list of tags.
960

    
961
Example::
962

    
963
    ["tag1", "tag2", "tag3"]
964

    
965

    
966
.. _rapi-res-networks-network_name-tags+put:
967

    
968
``PUT``
969
~~~~~~~
970

    
971
Add a set of tags.
972

    
973
The request as a list of strings should be ``PUT`` to this URI. The
974
result will be a job id.
975

    
976
It supports the ``dry-run`` argument.
977

    
978

    
979
.. _rapi-res-networks-network_name-tags+delete:
980

    
981
``DELETE``
982
~~~~~~~~~~
983

    
984
Delete a tag.
985

    
986
In order to delete a set of tags, the DELETE request should be addressed
987
to URI like::
988

    
989
    /tags?tag=[tag]&tag=[tag]
990

    
991
It supports the ``dry-run`` argument.
992

    
993

    
994
.. _rapi-res-instances-multi-alloc:
995

    
996
``/2/instances-multi-alloc``
997
++++++++++++++++++++++++++++
998

    
999
Tries to allocate multiple instances.
1000

    
1001
.. rapi_resource_details:: /2/instances-multi-alloc
1002

    
1003

    
1004
.. _rapi-res-instances-multi-alloc+post:
1005

    
1006
``POST``
1007
~~~~~~~~
1008

    
1009
The parameters:
1010

    
1011
.. opcode_params:: OP_INSTANCE_MULTI_ALLOC
1012

    
1013
Job result:
1014

    
1015
.. opcode_result:: OP_INSTANCE_MULTI_ALLOC
1016

    
1017

    
1018
.. _rapi-res-instances:
1019

    
1020
``/2/instances``
1021
++++++++++++++++
1022

    
1023
The instances resource.
1024

    
1025
.. rapi_resource_details:: /2/instances
1026

    
1027

    
1028
.. _rapi-res-instances+get:
1029

    
1030
``GET``
1031
~~~~~~~
1032

    
1033
Returns a list of all available instances.
1034

    
1035
Example::
1036

    
1037
    [
1038
      {
1039
        "name": "web.example.com",
1040
        "uri": "\/instances\/web.example.com"
1041
      },
1042
      {
1043
        "name": "mail.example.com",
1044
        "uri": "\/instances\/mail.example.com"
1045
      }
1046
    ]
1047

    
1048
If the optional bool *bulk* argument is provided and set to a true value
1049
(i.e ``?bulk=1``), the output contains detailed information about
1050
instances as a list.
1051

    
1052
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.I_FIELDS))`.
1053

    
1054
Example::
1055

    
1056
    [
1057
      {
1058
        "status": "running",
1059
        "disk_usage": 20480,
1060
        "nic.bridges": [
1061
          "xen-br0"
1062
        ],
1063
        "name": "web.example.com",
1064
        "tags": ["tag1", "tag2"],
1065
        "beparams": {
1066
          "vcpus": 2,
1067
          "memory": 512
1068
        },
1069
        "disk.sizes": [
1070
          20480
1071
        ],
1072
        "pnode": "node1.example.com",
1073
        "nic.macs": ["01:23:45:67:89:01"],
1074
        "snodes": ["node2.example.com"],
1075
        "disk_template": "drbd",
1076
        "admin_state": true,
1077
        "os": "debian-etch",
1078
        "oper_state": true,
1079
1080
      },
1081
1082
    ]
1083

    
1084

    
1085
.. _rapi-res-instances+post:
1086

    
1087
``POST``
1088
~~~~~~~~
1089

    
1090
Creates an instance.
1091

    
1092
If the optional bool *dry-run* argument is provided, the job will not be
1093
actually executed, only the pre-execution checks will be done. Query-ing
1094
the job result will return, in both dry-run and normal case, the list of
1095
nodes selected for the instance.
1096

    
1097
Returns: a job ID that can be used later for polling.
1098

    
1099
Body parameters:
1100

    
1101
``__version__`` (int, required)
1102
  Must be ``1`` (older Ganeti versions used a different format for
1103
  instance creation requests, version ``0``, but that format is no
1104
  longer supported)
1105

    
1106
.. opcode_params:: OP_INSTANCE_CREATE
1107

    
1108
Earlier versions used parameters named ``name`` and ``os``. These have
1109
been replaced by ``instance_name`` and ``os_type`` to match the
1110
underlying opcode. The old names can still be used.
1111

    
1112
Job result:
1113

    
1114
.. opcode_result:: OP_INSTANCE_CREATE
1115

    
1116

    
1117
.. _rapi-res-instances-instance_name:
1118

    
1119
``/2/instances/[instance_name]``
1120
++++++++++++++++++++++++++++++++
1121

    
1122
Instance-specific resource.
1123

    
1124
.. rapi_resource_details:: /2/instances/[instance_name]
1125

    
1126

    
1127
.. _rapi-res-instances-instance_name+get:
1128

    
1129
``GET``
1130
~~~~~~~
1131

    
1132
Returns information about an instance, similar to the bulk output from
1133
the instance list.
1134

    
1135
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.I_FIELDS))`.
1136

    
1137

    
1138
.. _rapi-res-instances-instance_name+delete:
1139

    
1140
``DELETE``
1141
~~~~~~~~~~
1142

    
1143
Deletes an instance.
1144

    
1145
It supports the ``dry-run`` argument.
1146

    
1147
Job result:
1148

    
1149
.. opcode_result:: OP_INSTANCE_REMOVE
1150

    
1151

    
1152
.. _rapi-res-instances-instance_name-info:
1153

    
1154
``/2/instances/[instance_name]/info``
1155
+++++++++++++++++++++++++++++++++++++++
1156

    
1157
.. rapi_resource_details:: /2/instances/[instance_name]/info
1158

    
1159

    
1160
.. _rapi-res-instances-instance_name-info+get:
1161

    
1162
``GET``
1163
~~~~~~~
1164

    
1165
Requests detailed information about the instance. An optional parameter,
1166
``static`` (bool), can be set to return only static information from the
1167
configuration without querying the instance's nodes. The result will be
1168
a job id.
1169

    
1170
Job result:
1171

    
1172
.. opcode_result:: OP_INSTANCE_QUERY_DATA
1173

    
1174

    
1175
.. _rapi-res-instances-instance_name-reboot:
1176

    
1177
``/2/instances/[instance_name]/reboot``
1178
+++++++++++++++++++++++++++++++++++++++
1179

    
1180
Reboots URI for an instance.
1181

    
1182
.. rapi_resource_details:: /2/instances/[instance_name]/reboot
1183

    
1184

    
1185
.. _rapi-res-instances-instance_name-reboot+post:
1186

    
1187
``POST``
1188
~~~~~~~~
1189

    
1190
Reboots the instance.
1191

    
1192
The URI takes optional ``type=soft|hard|full`` and
1193
``ignore_secondaries=0|1`` parameters.
1194

    
1195
``type`` defines the reboot type. ``soft`` is just a normal reboot,
1196
without terminating the hypervisor. ``hard`` means full shutdown
1197
(including terminating the hypervisor process) and startup again.
1198
``full`` is like ``hard`` but also recreates the configuration from
1199
ground up as if you would have done a ``gnt-instance shutdown`` and
1200
``gnt-instance start`` on it.
1201

    
1202
``ignore_secondaries`` is a bool argument indicating if we start the
1203
instance even if secondary disks are failing.
1204

    
1205
It supports the ``dry-run`` argument.
1206

    
1207
Job result:
1208

    
1209
.. opcode_result:: OP_INSTANCE_REBOOT
1210

    
1211

    
1212
.. _rapi-res-instances-instance_name-shutdown:
1213

    
1214
``/2/instances/[instance_name]/shutdown``
1215
+++++++++++++++++++++++++++++++++++++++++
1216

    
1217
Instance shutdown URI.
1218

    
1219
.. rapi_resource_details:: /2/instances/[instance_name]/shutdown
1220

    
1221

    
1222
.. _rapi-res-instances-instance_name-shutdown+put:
1223

    
1224
``PUT``
1225
~~~~~~~
1226

    
1227
Shutdowns an instance.
1228

    
1229
It supports the ``dry-run`` argument.
1230

    
1231
.. opcode_params:: OP_INSTANCE_SHUTDOWN
1232
   :exclude: instance_name, dry_run
1233

    
1234
Job result:
1235

    
1236
.. opcode_result:: OP_INSTANCE_SHUTDOWN
1237

    
1238

    
1239
.. _rapi-res-instances-instance_name-startup:
1240

    
1241
``/2/instances/[instance_name]/startup``
1242
++++++++++++++++++++++++++++++++++++++++
1243

    
1244
Instance startup URI.
1245

    
1246
.. rapi_resource_details:: /2/instances/[instance_name]/startup
1247

    
1248

    
1249
.. _rapi-res-instances-instance_name-startup+put:
1250

    
1251
``PUT``
1252
~~~~~~~
1253

    
1254
Startup an instance.
1255

    
1256
The URI takes an optional ``force=1|0`` parameter to start the
1257
instance even if secondary disks are failing.
1258

    
1259
It supports the ``dry-run`` argument.
1260

    
1261
Job result:
1262

    
1263
.. opcode_result:: OP_INSTANCE_STARTUP
1264

    
1265

    
1266
.. _rapi-res-instances-instance_name-reinstall:
1267

    
1268
``/2/instances/[instance_name]/reinstall``
1269
++++++++++++++++++++++++++++++++++++++++++++++
1270

    
1271
Installs the operating system again.
1272

    
1273
.. rapi_resource_details:: /2/instances/[instance_name]/reinstall
1274

    
1275

    
1276
.. _rapi-res-instances-instance_name-reinstall+post:
1277

    
1278
``POST``
1279
~~~~~~~~
1280

    
1281
Returns a job ID.
1282

    
1283
Body parameters:
1284

    
1285
``os`` (string, required)
1286
  Instance operating system.
1287
``start`` (bool, defaults to true)
1288
  Whether to start instance after reinstallation.
1289
``osparams`` (dict)
1290
  Dictionary with (temporary) OS parameters.
1291

    
1292
For backwards compatbility, this resource also takes the query
1293
parameters ``os`` (OS template name) and ``nostartup`` (bool). New
1294
clients should use the body parameters.
1295

    
1296

    
1297
.. _rapi-res-instances-instance_name-replace-disks:
1298

    
1299
``/2/instances/[instance_name]/replace-disks``
1300
++++++++++++++++++++++++++++++++++++++++++++++
1301

    
1302
Replaces disks on an instance.
1303

    
1304
.. rapi_resource_details:: /2/instances/[instance_name]/replace-disks
1305

    
1306

    
1307
.. _rapi-res-instances-instance_name-replace-disks+post:
1308

    
1309
``POST``
1310
~~~~~~~~
1311

    
1312
Returns a job ID.
1313

    
1314
Body parameters:
1315

    
1316
.. opcode_params:: OP_INSTANCE_REPLACE_DISKS
1317
   :exclude: instance_name
1318

    
1319
Ganeti 2.4 and below used query parameters. Those are deprecated and
1320
should no longer be used.
1321

    
1322
Job result:
1323

    
1324
.. opcode_result:: OP_INSTANCE_REPLACE_DISKS
1325

    
1326

    
1327
.. _rapi-res-instances-instance_name-activate-disks:
1328

    
1329
``/2/instances/[instance_name]/activate-disks``
1330
+++++++++++++++++++++++++++++++++++++++++++++++
1331

    
1332
Activate disks on an instance.
1333

    
1334
.. rapi_resource_details:: /2/instances/[instance_name]/activate-disks
1335

    
1336

    
1337
.. _rapi-res-instances-instance_name-activate-disks+put:
1338

    
1339
``PUT``
1340
~~~~~~~
1341

    
1342
Takes the bool parameter ``ignore_size``. When set ignore the recorded
1343
size (useful for forcing activation when recorded size is wrong).
1344

    
1345
Job result:
1346

    
1347
.. opcode_result:: OP_INSTANCE_ACTIVATE_DISKS
1348

    
1349

    
1350
.. _rapi-res-instances-instance_name-deactivate-disks:
1351

    
1352
``/2/instances/[instance_name]/deactivate-disks``
1353
+++++++++++++++++++++++++++++++++++++++++++++++++
1354

    
1355
Deactivate disks on an instance.
1356

    
1357
.. rapi_resource_details:: /2/instances/[instance_name]/deactivate-disks
1358

    
1359

    
1360
.. _rapi-res-instances-instance_name-deactivate-disks+put:
1361

    
1362
``PUT``
1363
~~~~~~~
1364

    
1365
Takes no parameters.
1366

    
1367
Job result:
1368

    
1369
.. opcode_result:: OP_INSTANCE_DEACTIVATE_DISKS
1370

    
1371

    
1372
.. _rapi-res-instances-instance_name-recreate-disks:
1373

    
1374
``/2/instances/[instance_name]/recreate-disks``
1375
+++++++++++++++++++++++++++++++++++++++++++++++++
1376

    
1377
Recreate disks of an instance.
1378

    
1379
.. rapi_resource_details:: /2/instances/[instance_name]/recreate-disks
1380

    
1381

    
1382
.. _rapi-res-instances-instance_name-recreate-disks+post:
1383

    
1384
``POST``
1385
~~~~~~~~
1386

    
1387
Returns a job ID.
1388

    
1389
Body parameters:
1390

    
1391
.. opcode_params:: OP_INSTANCE_RECREATE_DISKS
1392
   :exclude: instance_name
1393

    
1394
Job result:
1395

    
1396
.. opcode_result:: OP_INSTANCE_RECREATE_DISKS
1397

    
1398

    
1399
.. _rapi-res-instances-instance_name-disk-disk_index-grow:
1400

    
1401
``/2/instances/[instance_name]/disk/[disk_index]/grow``
1402
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
1403

    
1404
Grows one disk of an instance.
1405

    
1406
.. rapi_resource_details:: /2/instances/[instance_name]/disk/[disk_index]/grow
1407

    
1408

    
1409
.. _rapi-res-instances-instance_name-disk-disk_index-grow+post:
1410

    
1411
``POST``
1412
~~~~~~~~
1413

    
1414
Returns a job ID.
1415

    
1416
Body parameters:
1417

    
1418
.. opcode_params:: OP_INSTANCE_GROW_DISK
1419
   :exclude: instance_name, disk
1420

    
1421
Job result:
1422

    
1423
.. opcode_result:: OP_INSTANCE_GROW_DISK
1424

    
1425

    
1426
.. _rapi-res-instances-instance_name-prepare-export:
1427

    
1428
``/2/instances/[instance_name]/prepare-export``
1429
+++++++++++++++++++++++++++++++++++++++++++++++++
1430

    
1431
Prepares an export of an instance.
1432

    
1433
.. rapi_resource_details:: /2/instances/[instance_name]/prepare-export
1434

    
1435

    
1436
.. _rapi-res-instances-instance_name-prepare-export+put:
1437

    
1438
``PUT``
1439
~~~~~~~
1440

    
1441
Takes one parameter, ``mode``, for the export mode. Returns a job ID.
1442

    
1443
Job result:
1444

    
1445
.. opcode_result:: OP_BACKUP_PREPARE
1446

    
1447

    
1448
.. _rapi-res-instances-instance_name-export:
1449

    
1450
``/2/instances/[instance_name]/export``
1451
+++++++++++++++++++++++++++++++++++++++++++++++++
1452

    
1453
Exports an instance.
1454

    
1455
.. rapi_resource_details:: /2/instances/[instance_name]/export
1456

    
1457

    
1458
.. _rapi-res-instances-instance_name-export+put:
1459

    
1460
``PUT``
1461
~~~~~~~
1462

    
1463
Returns a job ID.
1464

    
1465
Body parameters:
1466

    
1467
.. opcode_params:: OP_BACKUP_EXPORT
1468
   :exclude: instance_name
1469
   :alias: target_node=destination
1470

    
1471
Job result:
1472

    
1473
.. opcode_result:: OP_BACKUP_EXPORT
1474

    
1475

    
1476
.. _rapi-res-instances-instance_name-migrate:
1477

    
1478
``/2/instances/[instance_name]/migrate``
1479
++++++++++++++++++++++++++++++++++++++++
1480

    
1481
Migrates an instance.
1482

    
1483
.. rapi_resource_details:: /2/instances/[instance_name]/migrate
1484

    
1485

    
1486
.. _rapi-res-instances-instance_name-migrate+put:
1487

    
1488
``PUT``
1489
~~~~~~~
1490

    
1491
Returns a job ID.
1492

    
1493
Body parameters:
1494

    
1495
.. opcode_params:: OP_INSTANCE_MIGRATE
1496
   :exclude: instance_name, live
1497

    
1498
Job result:
1499

    
1500
.. opcode_result:: OP_INSTANCE_MIGRATE
1501

    
1502

    
1503
.. _rapi-res-instances-instance_name-failover:
1504

    
1505
``/2/instances/[instance_name]/failover``
1506
+++++++++++++++++++++++++++++++++++++++++
1507

    
1508
Does a failover of an instance.
1509

    
1510
.. rapi_resource_details:: /2/instances/[instance_name]/failover
1511

    
1512

    
1513
.. _rapi-res-instances-instance_name-failover+put:
1514

    
1515
``PUT``
1516
~~~~~~~
1517

    
1518
Returns a job ID.
1519

    
1520
Body parameters:
1521

    
1522
.. opcode_params:: OP_INSTANCE_FAILOVER
1523
   :exclude: instance_name
1524

    
1525
Job result:
1526

    
1527
.. opcode_result:: OP_INSTANCE_FAILOVER
1528

    
1529

    
1530
.. _rapi-res-instances-instance_name-rename:
1531

    
1532
``/2/instances/[instance_name]/rename``
1533
++++++++++++++++++++++++++++++++++++++++
1534

    
1535
Renames an instance.
1536

    
1537
.. rapi_resource_details:: /2/instances/[instance_name]/rename
1538

    
1539

    
1540
.. _rapi-res-instances-instance_name-rename+put:
1541

    
1542
``PUT``
1543
~~~~~~~
1544

    
1545
Returns a job ID.
1546

    
1547
Body parameters:
1548

    
1549
.. opcode_params:: OP_INSTANCE_RENAME
1550
   :exclude: instance_name
1551

    
1552
Job result:
1553

    
1554
.. opcode_result:: OP_INSTANCE_RENAME
1555

    
1556

    
1557
.. _rapi-res-instances-instance_name-modify:
1558

    
1559
``/2/instances/[instance_name]/modify``
1560
++++++++++++++++++++++++++++++++++++++++
1561

    
1562
Modifies an instance.
1563

    
1564
.. rapi_resource_details:: /2/instances/[instance_name]/modify
1565

    
1566

    
1567
.. _rapi-res-instances-instance_name-modify+put:
1568

    
1569
``PUT``
1570
~~~~~~~
1571

    
1572
Returns a job ID.
1573

    
1574
Body parameters:
1575

    
1576
.. opcode_params:: OP_INSTANCE_SET_PARAMS
1577
   :exclude: instance_name
1578

    
1579
Job result:
1580

    
1581
.. opcode_result:: OP_INSTANCE_SET_PARAMS
1582

    
1583

    
1584
.. _rapi-res-instances-instance_name-console:
1585

    
1586
``/2/instances/[instance_name]/console``
1587
++++++++++++++++++++++++++++++++++++++++
1588

    
1589
Request information for connecting to instance's console.
1590

    
1591
.. rapi_resource_details:: /2/instances/[instance_name]/console
1592

    
1593

    
1594
.. _rapi-res-instances-instance_name-console+get:
1595

    
1596
``GET``
1597
~~~~~~~
1598

    
1599
Returns a dictionary containing information about the instance's
1600
console. Contained keys:
1601

    
1602
.. pyassert::
1603

    
1604
   constants.CONS_ALL == frozenset([
1605
     constants.CONS_MESSAGE,
1606
     constants.CONS_SSH,
1607
     constants.CONS_VNC,
1608
     constants.CONS_SPICE,
1609
     ])
1610

    
1611
.. pyassert::
1612

    
1613
  frozenset(objects.InstanceConsole.GetAllSlots()) == frozenset([
1614
    "command",
1615
    "display",
1616
    "host",
1617
    "instance",
1618
    "kind",
1619
    "message",
1620
    "port",
1621
    "user",
1622
    ])
1623

    
1624

    
1625
``instance``
1626
  Instance name
1627
``kind``
1628
  Console type, one of :pyeval:`constants.CONS_SSH`,
1629
  :pyeval:`constants.CONS_VNC`, :pyeval:`constants.CONS_SPICE`
1630
  or :pyeval:`constants.CONS_MESSAGE`
1631
``message``
1632
  Message to display (:pyeval:`constants.CONS_MESSAGE` type only)
1633
``host``
1634
  Host to connect to (:pyeval:`constants.CONS_SSH`,
1635
  :pyeval:`constants.CONS_VNC` or :pyeval:`constants.CONS_SPICE` only)
1636
``port``
1637
  TCP port to connect to (:pyeval:`constants.CONS_VNC` or
1638
  :pyeval:`constants.CONS_SPICE` only)
1639
``user``
1640
  Username to use (:pyeval:`constants.CONS_SSH` only)
1641
``command``
1642
  Command to execute on machine (:pyeval:`constants.CONS_SSH` only)
1643
``display``
1644
  VNC display number (:pyeval:`constants.CONS_VNC` only)
1645

    
1646

    
1647
.. _rapi-res-instances-instance_name-tags:
1648

    
1649
``/2/instances/[instance_name]/tags``
1650
+++++++++++++++++++++++++++++++++++++
1651

    
1652
Manages per-instance tags.
1653

    
1654
.. rapi_resource_details:: /2/instances/[instance_name]/tags
1655

    
1656

    
1657
.. _rapi-res-instances-instance_name-tags+get:
1658

    
1659
``GET``
1660
~~~~~~~
1661

    
1662
Returns a list of tags.
1663

    
1664
Example::
1665

    
1666
    ["tag1", "tag2", "tag3"]
1667

    
1668

    
1669
.. _rapi-res-instances-instance_name-tags+put:
1670

    
1671
``PUT``
1672
~~~~~~~
1673

    
1674
Add a set of tags.
1675

    
1676
The request as a list of strings should be ``PUT`` to this URI. The
1677
result will be a job id.
1678

    
1679
It supports the ``dry-run`` argument.
1680

    
1681

    
1682
.. _rapi-res-instances-instance_name-tags+delete:
1683

    
1684
``DELETE``
1685
~~~~~~~~~~
1686

    
1687
Delete a tag.
1688

    
1689
In order to delete a set of tags, the DELETE request should be addressed
1690
to URI like::
1691

    
1692
    /tags?tag=[tag]&tag=[tag]
1693

    
1694
It supports the ``dry-run`` argument.
1695

    
1696

    
1697
.. _rapi-res-jobs:
1698

    
1699
``/2/jobs``
1700
+++++++++++
1701

    
1702
The ``/2/jobs`` resource.
1703

    
1704
.. rapi_resource_details:: /2/jobs
1705

    
1706

    
1707
.. _rapi-res-jobs+get:
1708

    
1709
``GET``
1710
~~~~~~~
1711

    
1712
Returns a dictionary of jobs.
1713

    
1714
Returns: a dictionary with jobs id and uri.
1715

    
1716
If the optional bool *bulk* argument is provided and set to a true value
1717
(i.e. ``?bulk=1``), the output contains detailed information about jobs
1718
as a list.
1719

    
1720
Returned fields for bulk requests (unlike other bulk requests, these
1721
fields are not the same as for per-job requests):
1722
:pyeval:`utils.CommaJoin(sorted(rlib2.J_FIELDS_BULK))`.
1723

    
1724

    
1725
.. _rapi-res-jobs-job_id:
1726

    
1727
``/2/jobs/[job_id]``
1728
++++++++++++++++++++
1729

    
1730
Individual job URI.
1731

    
1732
.. rapi_resource_details:: /2/jobs/[job_id]
1733

    
1734

    
1735
.. _rapi-res-jobs-job_id+get:
1736

    
1737
``GET``
1738
~~~~~~~
1739

    
1740
Returns a dictionary with job parameters, containing the fields
1741
:pyeval:`utils.CommaJoin(sorted(rlib2.J_FIELDS))`.
1742

    
1743
The result includes:
1744

    
1745
- id: job ID as a number
1746
- status: current job status as a string
1747
- ops: involved OpCodes as a list of dictionaries for each opcodes in
1748
  the job
1749
- opstatus: OpCodes status as a list
1750
- opresult: OpCodes results as a list
1751

    
1752
For a successful opcode, the ``opresult`` field corresponding to it will
1753
contain the raw result from its :term:`LogicalUnit`. In case an opcode
1754
has failed, its element in the opresult list will be a list of two
1755
elements:
1756

    
1757
- first element the error type (the Ganeti internal error name)
1758
- second element a list of either one or two elements:
1759

    
1760
  - the first element is the textual error description
1761
  - the second element, if any, will hold an error classification
1762

    
1763
The error classification is most useful for the ``OpPrereqError``
1764
error type - these errors happen before the OpCode has started
1765
executing, so it's possible to retry the OpCode without side
1766
effects. But whether it make sense to retry depends on the error
1767
classification:
1768

    
1769
.. pyassert::
1770

    
1771
   errors.ECODE_ALL == set([errors.ECODE_RESOLVER, errors.ECODE_NORES,
1772
     errors.ECODE_INVAL, errors.ECODE_STATE, errors.ECODE_NOENT,
1773
     errors.ECODE_EXISTS, errors.ECODE_NOTUNIQUE, errors.ECODE_FAULT,
1774
     errors.ECODE_ENVIRON, errors.ECODE_TEMP_NORES])
1775

    
1776
:pyeval:`errors.ECODE_RESOLVER`
1777
  Resolver errors. This usually means that a name doesn't exist in DNS,
1778
  so if it's a case of slow DNS propagation the operation can be retried
1779
  later.
1780

    
1781
:pyeval:`errors.ECODE_NORES`
1782
  Not enough resources (iallocator failure, disk space, memory,
1783
  etc.). If the resources on the cluster increase, the operation might
1784
  succeed.
1785

    
1786
:pyeval:`errors.ECODE_TEMP_NORES`
1787
  Simliar to :pyeval:`errors.ECODE_NORES`, but indicating the operation
1788
  should be attempted again after some time.
1789

    
1790
:pyeval:`errors.ECODE_INVAL`
1791
  Wrong arguments (at syntax level). The operation will not ever be
1792
  accepted unless the arguments change.
1793

    
1794
:pyeval:`errors.ECODE_STATE`
1795
  Wrong entity state. For example, live migration has been requested for
1796
  a down instance, or instance creation on an offline node. The
1797
  operation can be retried once the resource has changed state.
1798

    
1799
:pyeval:`errors.ECODE_NOENT`
1800
  Entity not found. For example, information has been requested for an
1801
  unknown instance.
1802

    
1803
:pyeval:`errors.ECODE_EXISTS`
1804
  Entity already exists. For example, instance creation has been
1805
  requested for an already-existing instance.
1806

    
1807
:pyeval:`errors.ECODE_NOTUNIQUE`
1808
  Resource not unique (e.g. MAC or IP duplication).
1809

    
1810
:pyeval:`errors.ECODE_FAULT`
1811
  Internal cluster error. For example, a node is unreachable but not set
1812
  offline, or the ganeti node daemons are not working, etc. A
1813
  ``gnt-cluster verify`` should be run.
1814

    
1815
:pyeval:`errors.ECODE_ENVIRON`
1816
  Environment error (e.g. node disk error). A ``gnt-cluster verify``
1817
  should be run.
1818

    
1819
Note that in the above list, by entity we refer to a node or instance,
1820
while by a resource we refer to an instance's disk, or NIC, etc.
1821

    
1822

    
1823
.. _rapi-res-jobs-job_id+delete:
1824

    
1825
``DELETE``
1826
~~~~~~~~~~
1827

    
1828
Cancel a not-yet-started job.
1829

    
1830

    
1831
.. _rapi-res-jobs-job_id-wait:
1832

    
1833
``/2/jobs/[job_id]/wait``
1834
+++++++++++++++++++++++++
1835

    
1836
.. rapi_resource_details:: /2/jobs/[job_id]/wait
1837

    
1838

    
1839
.. _rapi-res-jobs-job_id-wait+get:
1840

    
1841
``GET``
1842
~~~~~~~
1843

    
1844
Waits for changes on a job. Takes the following body parameters in a
1845
dict:
1846

    
1847
``fields``
1848
  The job fields on which to watch for changes
1849

    
1850
``previous_job_info``
1851
  Previously received field values or None if not yet available
1852

    
1853
``previous_log_serial``
1854
  Highest log serial number received so far or None if not yet
1855
  available
1856

    
1857
Returns None if no changes have been detected and a dict with two keys,
1858
``job_info`` and ``log_entries`` otherwise.
1859

    
1860

    
1861
.. _rapi-res-nodes:
1862

    
1863
``/2/nodes``
1864
++++++++++++
1865

    
1866
Nodes resource.
1867

    
1868
.. rapi_resource_details:: /2/nodes
1869

    
1870

    
1871
.. _rapi-res-nodes+get:
1872

    
1873
``GET``
1874
~~~~~~~
1875

    
1876
Returns a list of all nodes.
1877

    
1878
Example::
1879

    
1880
    [
1881
      {
1882
        "id": "node1.example.com",
1883
        "uri": "\/nodes\/node1.example.com"
1884
      },
1885
      {
1886
        "id": "node2.example.com",
1887
        "uri": "\/nodes\/node2.example.com"
1888
      }
1889
    ]
1890

    
1891
If the optional bool *bulk* argument is provided and set to a true value
1892
(i.e ``?bulk=1``), the output contains detailed information about nodes
1893
as a list.
1894

    
1895
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.N_FIELDS))`.
1896

    
1897
Example::
1898

    
1899
    [
1900
      {
1901
        "pinst_cnt": 1,
1902
        "mfree": 31280,
1903
        "mtotal": 32763,
1904
        "name": "www.example.com",
1905
        "tags": [],
1906
        "mnode": 512,
1907
        "dtotal": 5246208,
1908
        "sinst_cnt": 2,
1909
        "dfree": 5171712,
1910
        "offline": false,
1911
1912
      },
1913
1914
    ]
1915

    
1916

    
1917
.. _rapi-res-nodes-node_name:
1918

    
1919
``/2/nodes/[node_name]``
1920
+++++++++++++++++++++++++++++++++
1921

    
1922
Returns information about a node.
1923

    
1924
.. rapi_resource_details:: /2/nodes/[node_name]
1925

    
1926

    
1927
.. _rapi-res-nodes-node_name+get:
1928

    
1929
``GET``
1930
~~~~~~~
1931

    
1932
Returned fields: :pyeval:`utils.CommaJoin(sorted(rlib2.N_FIELDS))`.
1933

    
1934

    
1935

    
1936
.. _rapi-res-nodes-node_name-powercycle:
1937

    
1938
``/2/nodes/[node_name]/powercycle``
1939
+++++++++++++++++++++++++++++++++++
1940

    
1941
Powercycles a node.
1942

    
1943
.. rapi_resource_details:: /2/nodes/[node_name]/powercycle
1944

    
1945

    
1946
.. _rapi-res-nodes-node_name-powercycle+post:
1947

    
1948
``POST``
1949
~~~~~~~~
1950

    
1951
Returns a job ID.
1952

    
1953
Job result:
1954

    
1955
.. opcode_result:: OP_NODE_POWERCYCLE
1956

    
1957

    
1958
.. _rapi-res-nodes-node_name-evacuate:
1959

    
1960
``/2/nodes/[node_name]/evacuate``
1961
+++++++++++++++++++++++++++++++++
1962

    
1963
Evacuates instances off a node.
1964

    
1965
.. rapi_resource_details:: /2/nodes/[node_name]/evacuate
1966

    
1967

    
1968
.. _rapi-res-nodes-node_name-evacuate+post:
1969

    
1970
``POST``
1971
~~~~~~~~
1972

    
1973
Returns a job ID. The result of the job will contain the IDs of the
1974
individual jobs submitted to evacuate the node.
1975

    
1976
Body parameters:
1977

    
1978
.. opcode_params:: OP_NODE_EVACUATE
1979
   :exclude: nodes
1980

    
1981
Up to and including Ganeti 2.4 query arguments were used. Those are no
1982
longer supported. The new request can be detected by the presence of the
1983
:pyeval:`rlib2._NODE_EVAC_RES1` feature string.
1984

    
1985
Job result:
1986

    
1987
.. opcode_result:: OP_NODE_EVACUATE
1988

    
1989

    
1990
.. _rapi-res-nodes-node_name-migrate:
1991

    
1992
``/2/nodes/[node_name]/migrate``
1993
+++++++++++++++++++++++++++++++++
1994

    
1995
Migrates all primary instances from a node.
1996

    
1997
.. rapi_resource_details:: /2/nodes/[node_name]/migrate
1998

    
1999

    
2000
.. _rapi-res-nodes-node_name-migrate+post:
2001

    
2002
``POST``
2003
~~~~~~~~
2004

    
2005
If no mode is explicitly specified, each instances' hypervisor default
2006
migration mode will be used. Body parameters:
2007

    
2008
.. opcode_params:: OP_NODE_MIGRATE
2009
   :exclude: node_name
2010

    
2011
The query arguments used up to and including Ganeti 2.4 are deprecated
2012
and should no longer be used. The new request format can be detected by
2013
the presence of the :pyeval:`rlib2._NODE_MIGRATE_REQV1` feature string.
2014

    
2015
Job result:
2016

    
2017
.. opcode_result:: OP_NODE_MIGRATE
2018

    
2019

    
2020
.. _rapi-res-nodes-node_name-role:
2021

    
2022
``/2/nodes/[node_name]/role``
2023
+++++++++++++++++++++++++++++
2024

    
2025
Manages node role.
2026

    
2027
.. rapi_resource_details:: /2/nodes/[node_name]/role
2028

    
2029
The role is always one of the following:
2030

    
2031
  - drained
2032
  - master-candidate
2033
  - offline
2034
  - regular
2035

    
2036
Note that the 'master' role is a special, and currently it can't be
2037
modified via RAPI, only via the command line (``gnt-cluster
2038
master-failover``).
2039

    
2040

    
2041
.. _rapi-res-nodes-node_name-role+get:
2042

    
2043
``GET``
2044
~~~~~~~
2045

    
2046
Returns the current node role.
2047

    
2048
Example::
2049

    
2050
    "master-candidate"
2051

    
2052

    
2053
.. _rapi-res-nodes-node_name-role+put:
2054

    
2055
``PUT``
2056
~~~~~~~
2057

    
2058
Change the node role.
2059

    
2060
The request is a string which should be PUT to this URI. The result will
2061
be a job id.
2062

    
2063
It supports the bool ``force`` argument.
2064

    
2065
Job result:
2066

    
2067
.. opcode_result:: OP_NODE_SET_PARAMS
2068

    
2069

    
2070
.. _rapi-res-nodes-node_name-modify:
2071

    
2072
``/2/nodes/[node_name]/modify``
2073
+++++++++++++++++++++++++++++++
2074

    
2075
Modifies the parameters of a node.
2076

    
2077
.. rapi_resource_details:: /2/nodes/[node_name]/modify
2078

    
2079

    
2080
.. _rapi-res-nodes-node_name-modify+post:
2081

    
2082
``POST``
2083
~~~~~~~~
2084

    
2085
Returns a job ID.
2086

    
2087
Body parameters:
2088

    
2089
.. opcode_params:: OP_NODE_SET_PARAMS
2090
   :exclude: node_name
2091

    
2092
Job result:
2093

    
2094
.. opcode_result:: OP_NODE_SET_PARAMS
2095

    
2096

    
2097
.. _rapi-res-nodes-node_name-storage:
2098

    
2099
``/2/nodes/[node_name]/storage``
2100
++++++++++++++++++++++++++++++++
2101

    
2102
Manages storage units on the node.
2103

    
2104
.. rapi_resource_details:: /2/nodes/[node_name]/storage
2105

    
2106

    
2107
.. _rapi-res-nodes-node_name-storage+get:
2108

    
2109
``GET``
2110
~~~~~~~
2111

    
2112
.. pyassert::
2113

    
2114
   constants.STS_REPORT == set([constants.ST_FILE,
2115
                                constants.ST_LVM_PV,
2116
                                constants.ST_LVM_VG])
2117

    
2118
Requests a list of storage units on a node. Requires the parameters
2119
``storage_type`` for storage types that support space reporting
2120
(one of :pyeval:`constants.ST_FILE`, :pyeval:`constants.ST_LVM_PV`
2121
or :pyeval:`constants.ST_LVM_VG`) and ``output_fields``. The result
2122
will be a job id, using which the result can be retrieved.
2123

    
2124

    
2125
.. _rapi-res-nodes-node_name-storage-modify:
2126

    
2127
``/2/nodes/[node_name]/storage/modify``
2128
+++++++++++++++++++++++++++++++++++++++
2129

    
2130
Modifies storage units on the node.
2131

    
2132
.. rapi_resource_details:: /2/nodes/[node_name]/storage/modify
2133

    
2134

    
2135
.. _rapi-res-nodes-node_name-storage-modify+put:
2136

    
2137
``PUT``
2138
~~~~~~~
2139

    
2140
Modifies parameters of storage units on the node. Requires the
2141
parameters ``storage_type`` (one of :pyeval:`constants.ST_FILE`,
2142
:pyeval:`constants.ST_LVM_PV` or :pyeval:`constants.ST_LVM_VG`)
2143
and ``name`` (name of the storage unit).  Parameters can be passed
2144
additionally. Currently only :pyeval:`constants.SF_ALLOCATABLE` (bool)
2145
is supported. The result will be a job id.
2146

    
2147
Job result:
2148

    
2149
.. opcode_result:: OP_NODE_MODIFY_STORAGE
2150

    
2151

    
2152
.. _rapi-res-nodes-node_name-storage-repair:
2153

    
2154
``/2/nodes/[node_name]/storage/repair``
2155
+++++++++++++++++++++++++++++++++++++++
2156

    
2157
Repairs a storage unit on the node.
2158

    
2159
.. rapi_resource_details:: /2/nodes/[node_name]/storage/repair
2160

    
2161

    
2162
.. _rapi-res-nodes-node_name-storage-repair+put:
2163

    
2164
``PUT``
2165
~~~~~~~
2166

    
2167
.. pyassert::
2168

    
2169
   constants.VALID_STORAGE_OPERATIONS == {
2170
    constants.ST_LVM_VG: set([constants.SO_FIX_CONSISTENCY]),
2171
    }
2172

    
2173
Repairs a storage unit on the node. Requires the parameters
2174
``storage_type`` (currently only :pyeval:`constants.ST_LVM_VG` can be
2175
repaired) and ``name`` (name of the storage unit). The result will be a
2176
job id.
2177

    
2178
Job result:
2179

    
2180
.. opcode_result:: OP_REPAIR_NODE_STORAGE
2181

    
2182

    
2183
.. _rapi-res-nodes-node_name-tags:
2184

    
2185
``/2/nodes/[node_name]/tags``
2186
+++++++++++++++++++++++++++++
2187

    
2188
Manages per-node tags.
2189

    
2190
.. rapi_resource_details:: /2/nodes/[node_name]/tags
2191

    
2192

    
2193
.. _rapi-res-nodes-node_name-tags+get:
2194

    
2195
``GET``
2196
~~~~~~~
2197

    
2198
Returns a list of tags.
2199

    
2200
Example::
2201

    
2202
    ["tag1", "tag2", "tag3"]
2203

    
2204

    
2205
.. _rapi-res-nodes-node_name-tags+put:
2206

    
2207
``PUT``
2208
~~~~~~~
2209

    
2210
Add a set of tags.
2211

    
2212
The request as a list of strings should be PUT to this URI. The result
2213
will be a job id.
2214

    
2215
It supports the ``dry-run`` argument.
2216

    
2217

    
2218
.. _rapi-res-nodes-node_name-tags+delete:
2219

    
2220
``DELETE``
2221
~~~~~~~~~~
2222

    
2223
Deletes tags.
2224

    
2225
In order to delete a set of tags, the DELETE request should be addressed
2226
to URI like::
2227

    
2228
    /tags?tag=[tag]&tag=[tag]
2229

    
2230
It supports the ``dry-run`` argument.
2231

    
2232

    
2233
.. _rapi-res-query-resource:
2234

    
2235
``/2/query/[resource]``
2236
+++++++++++++++++++++++
2237

    
2238
Requests resource information. Available fields can be found in man
2239
pages and using ``/2/query/[resource]/fields``. The resource is one of
2240
:pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the :doc:`query2
2241
design document <design-query2>` for more details.
2242

    
2243
.. rapi_resource_details:: /2/query/[resource]
2244

    
2245

    
2246
.. _rapi-res-query-resource+get:
2247

    
2248
``GET``
2249
~~~~~~~
2250

    
2251
Returns list of included fields and actual data. Takes a query parameter
2252
named "fields", containing a comma-separated list of field names. Does
2253
not support filtering.
2254

    
2255

    
2256
.. _rapi-res-query-resource+put:
2257

    
2258
``PUT``
2259
~~~~~~~
2260

    
2261
Returns list of included fields and actual data. The list of requested
2262
fields can either be given as the query parameter "fields" or as a body
2263
parameter with the same name. The optional body parameter "filter" can
2264
be given and must be either ``null`` or a list containing filter
2265
operators.
2266

    
2267

    
2268
.. _rapi-res-query-resource-fields:
2269

    
2270
``/2/query/[resource]/fields``
2271
++++++++++++++++++++++++++++++
2272

    
2273
Request list of available fields for a resource. The resource is one of
2274
:pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the
2275
:doc:`query2 design document <design-query2>` for more details.
2276

    
2277
.. rapi_resource_details:: /2/query/[resource]/fields
2278

    
2279

    
2280
.. _rapi-res-query-resource-fields+get:
2281

    
2282
``GET``
2283
~~~~~~~
2284

    
2285
Returns a list of field descriptions for available fields. Takes an
2286
optional query parameter named "fields", containing a comma-separated
2287
list of field names.
2288

    
2289

    
2290
.. _rapi-res-os:
2291

    
2292
``/2/os``
2293
+++++++++
2294

    
2295
OS resource.
2296

    
2297
.. rapi_resource_details:: /2/os
2298

    
2299

    
2300
.. _rapi-res-os+get:
2301

    
2302
``GET``
2303
~~~~~~~
2304

    
2305
Return a list of all OSes.
2306

    
2307
Can return error 500 in case of a problem. Since this is a costly
2308
operation for Ganeti 2.0, it is not recommended to execute it too often.
2309

    
2310
Example::
2311

    
2312
    ["debian-etch"]
2313

    
2314

    
2315
.. _rapi-res-tags:
2316

    
2317
``/2/tags``
2318
+++++++++++
2319

    
2320
Manages cluster tags.
2321

    
2322
.. rapi_resource_details:: /2/tags
2323

    
2324

    
2325
.. _rapi-res-tags+get:
2326

    
2327
``GET``
2328
~~~~~~~
2329

    
2330
Returns the cluster tags.
2331

    
2332
Example::
2333

    
2334
    ["tag1", "tag2", "tag3"]
2335

    
2336

    
2337
.. _rapi-res-tags+put:
2338

    
2339
``PUT``
2340
~~~~~~~
2341

    
2342
Adds a set of tags.
2343

    
2344
The request as a list of strings should be PUT to this URI. The result
2345
will be a job id.
2346

    
2347
It supports the ``dry-run`` argument.
2348

    
2349

    
2350
.. _rapi-res-tags+delete:
2351

    
2352
``DELETE``
2353
~~~~~~~~~~
2354

    
2355
Deletes tags.
2356

    
2357
In order to delete a set of tags, the DELETE request should be addressed
2358
to URI like::
2359

    
2360
    /tags?tag=[tag]&tag=[tag]
2361

    
2362
It supports the ``dry-run`` argument.
2363

    
2364

    
2365
.. _rapi-res-version:
2366

    
2367
``/version``
2368
++++++++++++
2369

    
2370
The version resource.
2371

    
2372
This resource should be used to determine the remote API version and to
2373
adapt clients accordingly.
2374

    
2375
.. rapi_resource_details:: /version
2376

    
2377

    
2378
.. _rapi-res-version+get:
2379

    
2380
``GET``
2381
~~~~~~~
2382

    
2383
Returns the remote API version. Ganeti 1.2 returned ``1`` and Ganeti 2.0
2384
returns ``2``.
2385

    
2386

    
2387
.. _rapi-access-permissions:
2388

    
2389
Access permissions
2390
------------------
2391

    
2392
The following list describes the access permissions required for each
2393
resource. See :ref:`rapi-users` for more details.
2394

    
2395
.. rapi_access_table::
2396

    
2397

    
2398
.. vim: set textwidth=72 :
2399
.. Local Variables:
2400
.. mode: rst
2401
.. fill-column: 72
2402
.. End: