RAPI: Allow waiting for job changes
[ganeti-local] / doc / rapi.rst
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
20 Users and passwords
21 -------------------
22
23 ``ganeti-rapi`` reads users and passwords from a file (usually
24 ``/var/lib/ganeti/rapi_users``) on startup. After modifying the password
25 file, ``ganeti-rapi`` must be restarted.
26
27 Each line consists of two or three fields separated by whitespace. The
28 first two fields are for username and password. The third field is
29 optional and can be used to specify per-user options. Currently,
30 ``write`` is the only option supported and enables the user to execute
31 operations modifying the cluster. Lines starting with the hash sign
32 (``#``) are treated as comments.
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 not case sensitive.
41
42 Example::
43
44   # Give Jack and Fred read-only access
45   jack abc123
46   fred {cleartext}foo555
47
48   # Give write access to an imaginary instance creation script
49   autocreator xyz789 write
50
51   # Hashed password for Jessica
52   jessica {HA1}7046452df2cbb530877058712cf17bd4 write
53
54
55 .. [#pwhash] Using the MD5 hash of username, realm and password is
56    described in RFC2617_ ("HTTP Authentication"), sections 3.2.2.2 and
57    3.3. The reason for using it over another algorithm is forward
58    compatibility. If ``ganeti-rapi`` were to implement HTTP Digest
59    authentication in the future, the same hash could be used.
60    In the current version ``ganeti-rapi``'s realm, ``Ganeti Remote
61    API``, can only be changed by modifying the source code.
62
63
64 Protocol
65 --------
66
67 The protocol used is JSON_ over HTTP designed after the REST_ principle.
68 HTTP Basic authentication as per RFC2617_ is supported.
69
70 .. _JSON: http://www.json.org/
71 .. _REST: http://en.wikipedia.org/wiki/Representational_State_Transfer
72 .. _RFC2617: http://tools.ietf.org/rfc/rfc2617.txt
73
74
75 PUT or POST?
76 ------------
77
78 According to RFC2616 the main difference between PUT and POST is that
79 POST can create new resources but PUT can only create the resource the
80 URI was pointing to on the PUT request.
81
82 Unfortunately, due to historic reasons, the Ganeti RAPI library is not
83 consistent with this usage, so just use the methods as documented below
84 for each resource.
85
86 For more details have a look in the source code at
87 ``lib/rapi/rlib2.py``.
88
89
90 Generic parameter types
91 -----------------------
92
93 A few generic refered parameter types and the values they allow.
94
95 ``bool``
96 ++++++++
97
98 A boolean option will accept ``1`` or ``0`` as numbers but not
99 i.e. ``True`` or ``False``.
100
101 Generic parameters
102 ------------------
103
104 A few parameter mean the same thing across all resources which implement
105 it.
106
107 ``bulk``
108 ++++++++
109
110 Bulk-mode means that for the resources which usually return just a list
111 of child resources (e.g. ``/2/instances`` which returns just instance
112 names), the output will instead contain detailed data for all these
113 subresources. This is more efficient than query-ing the sub-resources
114 themselves.
115
116 ``dry-run``
117 +++++++++++
118
119 The boolean *dry-run* argument, if provided and set, signals to Ganeti
120 that the job should not be executed, only the pre-execution checks will
121 be done.
122
123 This is useful in trying to determine (without guarantees though, as in
124 the meantime the cluster state could have changed) if the operation is
125 likely to succeed or at least start executing.
126
127 ``force``
128 +++++++++++
129
130 Force operation to continue even if it will cause the cluster to become
131 inconsistent (e.g. because there are not enough master candidates).
132
133 Usage examples
134 --------------
135
136 You can access the API using your favorite programming language as long
137 as it supports network connections.
138
139 Shell
140 +++++
141
142 .. highlight:: sh
143
144 Using wget::
145
146    wget -q -O - https://CLUSTERNAME:5080/2/info
147
148 or curl::
149
150   curl https://CLUSTERNAME:5080/2/info
151
152
153 Python
154 ++++++
155
156 .. highlight:: python
157
158 ::
159
160   import urllib2
161   f = urllib2.urlopen('https://CLUSTERNAME:5080/2/info')
162   print f.read()
163
164
165 JavaScript
166 ++++++++++
167
168 .. warning:: While it's possible to use JavaScript, it poses several
169    potential problems, including browser blocking request due to
170    non-standard ports or different domain names. Fetching the data on
171    the webserver is easier.
172
173 .. highlight:: javascript
174
175 ::
176
177   var url = 'https://CLUSTERNAME:5080/2/info';
178   var info;
179   var xmlreq = new XMLHttpRequest();
180   xmlreq.onreadystatechange = function () {
181     if (xmlreq.readyState != 4) return;
182     if (xmlreq.status == 200) {
183       info = eval("(" + xmlreq.responseText + ")");
184       alert(info);
185     } else {
186       alert('Error fetching cluster info');
187     }
188     xmlreq = null;
189   };
190   xmlreq.open('GET', url, true);
191   xmlreq.send(null);
192
193 Resources
194 ---------
195
196 .. highlight:: javascript
197
198 ``/``
199 +++++
200
201 The root resource.
202
203 It supports the following commands: ``GET``.
204
205 ``GET``
206 ~~~~~~~
207
208 Shows the list of mapped resources.
209
210 Returns: a dictionary with 'name' and 'uri' keys for each of them.
211
212 ``/2``
213 ++++++
214
215 The ``/2`` resource, the root of the version 2 API.
216
217 It supports the following commands: ``GET``.
218
219 ``GET``
220 ~~~~~~~
221
222 Show the list of mapped resources.
223
224 Returns: a dictionary with ``name`` and ``uri`` keys for each of them.
225
226 ``/2/info``
227 +++++++++++
228
229 Cluster information resource.
230
231 It supports the following commands: ``GET``.
232
233 ``GET``
234 ~~~~~~~
235
236 Returns cluster information.
237
238 Example::
239
240   {
241     "config_version": 2000000,
242     "name": "cluster",
243     "software_version": "2.0.0~beta2",
244     "os_api_version": 10,
245     "export_version": 0,
246     "candidate_pool_size": 10,
247     "enabled_hypervisors": [
248       "fake"
249     ],
250     "hvparams": {
251       "fake": {}
252      },
253     "default_hypervisor": "fake",
254     "master": "node1.example.com",
255     "architecture": [
256       "64bit",
257       "x86_64"
258     ],
259     "protocol_version": 20,
260     "beparams": {
261       "default": {
262         "auto_balance": true,
263         "vcpus": 1,
264         "memory": 128
265        }
266       }
267     }
268
269
270 ``/2/redistribute-config``
271 ++++++++++++++++++++++++++
272
273 Redistribute configuration to all nodes.
274
275 It supports the following commands: ``PUT``.
276
277 ``PUT``
278 ~~~~~~~
279
280 Redistribute configuration to all nodes. The result will be a job id.
281
282
283 ``/2/instances``
284 ++++++++++++++++
285
286 The instances resource.
287
288 It supports the following commands: ``GET``, ``POST``.
289
290 ``GET``
291 ~~~~~~~
292
293 Returns a list of all available instances.
294
295 Example::
296
297     [
298       {
299         "name": "web.example.com",
300         "uri": "\/instances\/web.example.com"
301       },
302       {
303         "name": "mail.example.com",
304         "uri": "\/instances\/mail.example.com"
305       }
306     ]
307
308 If the optional bool *bulk* argument is provided and set to a true value
309 (i.e ``?bulk=1``), the output contains detailed information about
310 instances as a list.
311
312 Example::
313
314     [
315       {
316          "status": "running",
317          "disk_usage": 20480,
318          "nic.bridges": [
319            "xen-br0"
320           ],
321          "name": "web.example.com",
322          "tags": ["tag1", "tag2"],
323          "beparams": {
324            "vcpus": 2,
325            "memory": 512
326          },
327          "disk.sizes": [
328              20480
329          ],
330          "pnode": "node1.example.com",
331          "nic.macs": ["01:23:45:67:89:01"],
332          "snodes": ["node2.example.com"],
333          "disk_template": "drbd",
334          "admin_state": true,
335          "os": "debian-etch",
336          "oper_state": true
337       },
338       ...
339     ]
340
341
342 ``POST``
343 ~~~~~~~~
344
345 Creates an instance.
346
347 If the optional bool *dry-run* argument is provided, the job will not be
348 actually executed, only the pre-execution checks will be done. Query-ing
349 the job result will return, in both dry-run and normal case, the list of
350 nodes selected for the instance.
351
352 Returns: a job ID that can be used later for polling.
353
354 ``/2/instances/[instance_name]``
355 ++++++++++++++++++++++++++++++++
356
357 Instance-specific resource.
358
359 It supports the following commands: ``GET``, ``DELETE``.
360
361 ``GET``
362 ~~~~~~~
363
364 Returns information about an instance, similar to the bulk output from
365 the instance list.
366
367 ``DELETE``
368 ~~~~~~~~~~
369
370 Deletes an instance.
371
372 It supports the ``dry-run`` argument.
373
374
375 ``/2/instances/[instance_name]/info``
376 +++++++++++++++++++++++++++++++++++++++
377
378 It supports the following commands: ``GET``.
379
380 ``GET``
381 ~~~~~~~
382
383 Requests detailed information about the instance. An optional parameter,
384 ``static`` (bool), can be set to return only static information from the
385 configuration without querying the instance's nodes. The result will be
386 a job id.
387
388
389 ``/2/instances/[instance_name]/reboot``
390 +++++++++++++++++++++++++++++++++++++++
391
392 Reboots URI for an instance.
393
394 It supports the following commands: ``POST``.
395
396 ``POST``
397 ~~~~~~~~
398
399 Reboots the instance.
400
401 The URI takes optional ``type=soft|hard|full`` and
402 ``ignore_secondaries=0|1`` parameters.
403
404 ``type`` defines the reboot type. ``soft`` is just a normal reboot,
405 without terminating the hypervisor. ``hard`` means full shutdown
406 (including terminating the hypervisor process) and startup again.
407 ``full`` is like ``hard`` but also recreates the configuration from
408 ground up as if you would have done a ``gnt-instance shutdown`` and
409 ``gnt-instance start`` on it.
410
411 ``ignore_secondaries`` is a bool argument indicating if we start the
412 instance even if secondary disks are failing.
413
414 It supports the ``dry-run`` argument.
415
416
417 ``/2/instances/[instance_name]/shutdown``
418 +++++++++++++++++++++++++++++++++++++++++
419
420 Instance shutdown URI.
421
422 It supports the following commands: ``PUT``.
423
424 ``PUT``
425 ~~~~~~~
426
427 Shutdowns an instance.
428
429 It supports the ``dry-run`` argument.
430
431
432 ``/2/instances/[instance_name]/startup``
433 ++++++++++++++++++++++++++++++++++++++++
434
435 Instance startup URI.
436
437 It supports the following commands: ``PUT``.
438
439 ``PUT``
440 ~~~~~~~
441
442 Startup an instance.
443
444 The URI takes an optional ``force=1|0`` parameter to start the
445 instance even if secondary disks are failing.
446
447 It supports the ``dry-run`` argument.
448
449 ``/2/instances/[instance_name]/reinstall``
450 ++++++++++++++++++++++++++++++++++++++++++++++
451
452 Installs the operating system again.
453
454 It supports the following commands: ``POST``.
455
456 ``POST``
457 ~~~~~~~~
458
459 Takes the parameters ``os`` (OS template name) and ``nostartup`` (bool).
460
461
462 ``/2/instances/[instance_name]/replace-disks``
463 ++++++++++++++++++++++++++++++++++++++++++++++
464
465 Replaces disks on an instance.
466
467 It supports the following commands: ``POST``.
468
469 ``POST``
470 ~~~~~~~~
471
472 Takes the parameters ``mode`` (one of ``replace_on_primary``,
473 ``replace_on_secondary``, ``replace_new_secondary`` or
474 ``replace_auto``), ``disks`` (comma separated list of disk indexes),
475 ``remote_node`` and ``iallocator``.
476
477 Either ``remote_node`` or ``iallocator`` needs to be defined when using
478 ``mode=replace_new_secondary``.
479
480 ``mode`` is a mandatory parameter. ``replace_auto`` tries to determine
481 the broken disk(s) on its own and replacing it.
482
483
484 ``/2/instances/[instance_name]/activate-disks``
485 +++++++++++++++++++++++++++++++++++++++++++++++
486
487 Activate disks on an instance.
488
489 It supports the following commands: ``PUT``.
490
491 ``PUT``
492 ~~~~~~~
493
494 Takes the bool parameter ``ignore_size``. When set ignore the recorded
495 size (useful for forcing activation when recorded size is wrong).
496
497
498 ``/2/instances/[instance_name]/deactivate-disks``
499 +++++++++++++++++++++++++++++++++++++++++++++++++
500
501 Deactivate disks on an instance.
502
503 It supports the following commands: ``PUT``.
504
505 ``PUT``
506 ~~~~~~~
507
508 Takes no parameters.
509
510
511 ``/2/instances/[instance_name]/tags``
512 +++++++++++++++++++++++++++++++++++++
513
514 Manages per-instance tags.
515
516 It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
517
518 ``GET``
519 ~~~~~~~
520
521 Returns a list of tags.
522
523 Example::
524
525     ["tag1", "tag2", "tag3"]
526
527 ``PUT``
528 ~~~~~~~
529
530 Add a set of tags.
531
532 The request as a list of strings should be ``PUT`` to this URI. The
533 result will be a job id.
534
535 It supports the ``dry-run`` argument.
536
537
538 ``DELETE``
539 ~~~~~~~~~~
540
541 Delete a tag.
542
543 In order to delete a set of tags, the DELETE request should be addressed
544 to URI like::
545
546     /tags?tag=[tag]&tag=[tag]
547
548 It supports the ``dry-run`` argument.
549
550
551 ``/2/jobs``
552 +++++++++++
553
554 The ``/2/jobs`` resource.
555
556 It supports the following commands: ``GET``.
557
558 ``GET``
559 ~~~~~~~
560
561 Returns a dictionary of jobs.
562
563 Returns: a dictionary with jobs id and uri.
564
565 ``/2/jobs/[job_id]``
566 ++++++++++++++++++++
567
568
569 Individual job URI.
570
571 It supports the following commands: ``GET``, ``DELETE``.
572
573 ``GET``
574 ~~~~~~~
575
576 Returns a job status.
577
578 Returns: a dictionary with job parameters.
579
580 The result includes:
581
582 - id: job ID as a number
583 - status: current job status as a string
584 - ops: involved OpCodes as a list of dictionaries for each opcodes in
585   the job
586 - opstatus: OpCodes status as a list
587 - opresult: OpCodes results as a list
588
589 For a successful opcode, the ``opresult`` field corresponding to it will
590 contain the raw result from its :term:`LogicalUnit`. In case an opcode
591 has failed, its element in the opresult list will be a list of two
592 elements:
593
594 - first element the error type (the Ganeti internal error name)
595 - second element a list of either one or two elements:
596
597   - the first element is the textual error description
598   - the second element, if any, will hold an error classification
599
600 The error classification is most useful for the ``OpPrereqError``
601 error type - these errors happen before the OpCode has started
602 executing, so it's possible to retry the OpCode without side
603 effects. But whether it make sense to retry depends on the error
604 classification:
605
606 ``resolver_error``
607   Resolver errors. This usually means that a name doesn't exist in DNS,
608   so if it's a case of slow DNS propagation the operation can be retried
609   later.
610
611 ``insufficient_resources``
612   Not enough resources (iallocator failure, disk space, memory,
613   etc.). If the resources on the cluster increase, the operation might
614   succeed.
615
616 ``wrong_input``
617   Wrong arguments (at syntax level). The operation will not ever be
618   accepted unless the arguments change.
619
620 ``wrong_state``
621   Wrong entity state. For example, live migration has been requested for
622   a down instance, or instance creation on an offline node. The
623   operation can be retried once the resource has changed state.
624
625 ``unknown_entity``
626   Entity not found. For example, information has been requested for an
627   unknown instance.
628
629 ``already_exists``
630   Entity already exists. For example, instance creation has been
631   requested for an already-existing instance.
632
633 ``resource_not_unique``
634   Resource not unique (e.g. MAC or IP duplication).
635
636 ``internal_error``
637   Internal cluster error. For example, a node is unreachable but not set
638   offline, or the ganeti node daemons are not working, etc. A
639   ``gnt-cluster verify`` should be run.
640
641 ``environment_error``
642   Environment error (e.g. node disk error). A ``gnt-cluster verify``
643   should be run.
644
645 Note that in the above list, by entity we refer to a node or instance,
646 while by a resource we refer to an instance's disk, or NIC, etc.
647
648
649 ``DELETE``
650 ~~~~~~~~~~
651
652 Cancel a not-yet-started job.
653
654
655 ``/2/jobs/[job_id]/wait``
656 +++++++++++++++++++++++++
657
658 ``GET``
659 ~~~~~~~
660
661 Waits for changes on a job. Takes the following body parameters in a
662 dict:
663
664 ``fields``
665   The job fields on which to watch for changes.
666
667 ``previous_job_info``
668   Previously received field values or None if not yet available.
669
670 ``previous_log_serial``
671   Highest log serial number received so far or None if not yet
672   available.
673
674 Returns None if no changes have been detected and a dict with two keys,
675 ``job_info`` and ``log_entries`` otherwise.
676
677
678 ``/2/nodes``
679 ++++++++++++
680
681 Nodes resource.
682
683 It supports the following commands: ``GET``.
684
685 ``GET``
686 ~~~~~~~
687
688 Returns a list of all nodes.
689
690 Example::
691
692     [
693       {
694         "id": "node1.example.com",
695         "uri": "\/nodes\/node1.example.com"
696       },
697       {
698         "id": "node2.example.com",
699         "uri": "\/nodes\/node2.example.com"
700       }
701     ]
702
703 If the optional 'bulk' argument is provided and set to 'true' value (i.e
704 '?bulk=1'), the output contains detailed information about nodes as a
705 list.
706
707 Example::
708
709     [
710       {
711         "pinst_cnt": 1,
712         "mfree": 31280,
713         "mtotal": 32763,
714         "name": "www.example.com",
715         "tags": [],
716         "mnode": 512,
717         "dtotal": 5246208,
718         "sinst_cnt": 2,
719         "dfree": 5171712,
720         "offline": false
721       },
722       ...
723     ]
724
725 ``/2/nodes/[node_name]``
726 +++++++++++++++++++++++++++++++++
727
728 Returns information about a node.
729
730 It supports the following commands: ``GET``.
731
732 ``/2/nodes/[node_name]/evacuate``
733 +++++++++++++++++++++++++++++++++
734
735 Evacuates all secondary instances off a node.
736
737 It supports the following commands: ``POST``.
738
739 ``POST``
740 ~~~~~~~~
741
742 To evacuate a node, either one of the ``iallocator`` or ``remote_node``
743 parameters must be passed::
744
745     evacuate?iallocator=[iallocator]
746     evacuate?remote_node=[nodeX.example.com]
747
748 ``/2/nodes/[node_name]/migrate``
749 +++++++++++++++++++++++++++++++++
750
751 Migrates all primary instances from a node.
752
753 It supports the following commands: ``POST``.
754
755 ``POST``
756 ~~~~~~~~
757
758 No parameters are required, but the bool parameter ``live`` can be set
759 to use live migration (if available).
760
761     migrate?live=[0|1]
762
763 ``/2/nodes/[node_name]/role``
764 +++++++++++++++++++++++++++++
765
766 Manages node role.
767
768 It supports the following commands: ``GET``, ``PUT``.
769
770 The role is always one of the following:
771
772   - drained
773   - master
774   - master-candidate
775   - offline
776   - regular
777
778 ``GET``
779 ~~~~~~~
780
781 Returns the current node role.
782
783 Example::
784
785     "master-candidate"
786
787 ``PUT``
788 ~~~~~~~
789
790 Change the node role.
791
792 The request is a string which should be PUT to this URI. The result will
793 be a job id.
794
795 It supports the bool ``force`` argument.
796
797 ``/2/nodes/[node_name]/storage``
798 ++++++++++++++++++++++++++++++++
799
800 Manages storage units on the node.
801
802 ``GET``
803 ~~~~~~~
804
805 Requests a list of storage units on a node. Requires the parameters
806 ``storage_type`` (one of ``file``, ``lvm-pv`` or ``lvm-vg``) and
807 ``output_fields``. The result will be a job id, using which the result
808 can be retrieved.
809
810 ``/2/nodes/[node_name]/storage/modify``
811 +++++++++++++++++++++++++++++++++++++++
812
813 Modifies storage units on the node.
814
815 ``PUT``
816 ~~~~~~~
817
818 Modifies parameters of storage units on the node. Requires the
819 parameters ``storage_type`` (one of ``file``, ``lvm-pv`` or ``lvm-vg``)
820 and ``name`` (name of the storage unit).  Parameters can be passed
821 additionally. Currently only ``allocatable`` (bool) is supported. The
822 result will be a job id.
823
824 ``/2/nodes/[node_name]/storage/repair``
825 +++++++++++++++++++++++++++++++++++++++
826
827 Repairs a storage unit on the node.
828
829 ``PUT``
830 ~~~~~~~
831
832 Repairs a storage unit on the node. Requires the parameters
833 ``storage_type`` (currently only ``lvm-vg`` can be repaired) and
834 ``name`` (name of the storage unit). The result will be a job id.
835
836 ``/2/nodes/[node_name]/tags``
837 +++++++++++++++++++++++++++++
838
839 Manages per-node tags.
840
841 It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
842
843 ``GET``
844 ~~~~~~~
845
846 Returns a list of tags.
847
848 Example::
849
850     ["tag1", "tag2", "tag3"]
851
852 ``PUT``
853 ~~~~~~~
854
855 Add a set of tags.
856
857 The request as a list of strings should be PUT to this URI. The result
858 will be a job id.
859
860 It supports the ``dry-run`` argument.
861
862 ``DELETE``
863 ~~~~~~~~~~
864
865 Deletes tags.
866
867 In order to delete a set of tags, the DELETE request should be addressed
868 to URI like::
869
870     /tags?tag=[tag]&tag=[tag]
871
872 It supports the ``dry-run`` argument.
873
874
875 ``/2/os``
876 +++++++++
877
878 OS resource.
879
880 It supports the following commands: ``GET``.
881
882 ``GET``
883 ~~~~~~~
884
885 Return a list of all OSes.
886
887 Can return error 500 in case of a problem. Since this is a costly
888 operation for Ganeti 2.0, it is not recommended to execute it too often.
889
890 Example::
891
892     ["debian-etch"]
893
894 ``/2/tags``
895 +++++++++++
896
897 Manages cluster tags.
898
899 It supports the following commands: ``GET``, ``PUT``, ``DELETE``.
900
901 ``GET``
902 ~~~~~~~
903
904 Returns the cluster tags.
905
906 Example::
907
908     ["tag1", "tag2", "tag3"]
909
910 ``PUT``
911 ~~~~~~~
912
913 Adds a set of tags.
914
915 The request as a list of strings should be PUT to this URI. The result
916 will be a job id.
917
918 It supports the ``dry-run`` argument.
919
920
921 ``DELETE``
922 ~~~~~~~~~~
923
924 Deletes tags.
925
926 In order to delete a set of tags, the DELETE request should be addressed
927 to URI like::
928
929     /tags?tag=[tag]&tag=[tag]
930
931 It supports the ``dry-run`` argument.
932
933
934 ``/version``
935 ++++++++++++
936
937 The version resource.
938
939 This resource should be used to determine the remote API version and to
940 adapt clients accordingly.
941
942 It supports the following commands: ``GET``.
943
944 ``GET``
945 ~~~~~~~
946
947 Returns the remote API version. Ganeti 1.2 returned ``1`` and Ganeti 2.0
948 returns ``2``.
949
950 .. vim: set textwidth=72 :
951 .. Local Variables:
952 .. mode: rst
953 .. fill-column: 72
954 .. End: