Revision 4352bf6d
b/.gitignore | ||
---|---|---|
36 | 36 |
/doc/*.in |
37 | 37 |
/doc/*.pdf |
38 | 38 |
/doc/*.png |
39 |
/doc/rapi-resources.sgml
|
|
39 |
/doc/rapi-resources.gen
|
|
40 | 40 |
|
41 | 41 |
# doc/examples |
42 | 42 |
/doc/examples/bash_completion |
b/Makefile.am | ||
---|---|---|
44 | 44 |
doc/*.in \ |
45 | 45 |
doc/*.pdf \ |
46 | 46 |
$(patsubst %.dot,%.png,$(docdot)) \ |
47 |
doc/rapi-resources.sgml \
|
|
47 |
doc/rapi-resources.gen \
|
|
48 | 48 |
doc/examples/bash_completion \ |
49 | 49 |
doc/examples/ganeti.initd \ |
50 | 50 |
doc/examples/ganeti.cron \ |
... | ... | |
108 | 108 |
lib/http/server.py |
109 | 109 |
|
110 | 110 |
|
111 |
docsgml = \ |
|
112 |
doc/rapi.sgml |
|
113 |
|
|
114 | 111 |
docrst = \ |
115 | 112 |
doc/admin.rst \ |
116 | 113 |
doc/design-2.0.rst \ |
... | ... | |
123 | 120 |
doc/arch-2.0.dot |
124 | 121 |
|
125 | 122 |
doc_DATA = \ |
126 |
$(patsubst %.rst,%.html,$(docrst)) \ |
|
127 |
$(patsubst %.sgml,%.html,$(docsgml)) \ |
|
128 |
$(patsubst %.sgml,%.pdf,$(docsgml)) |
|
123 |
$(patsubst %.rst,%.html,$(docrst)) |
|
129 | 124 |
|
130 | 125 |
noinst_DATA = $(manhtml) |
131 | 126 |
|
... | ... | |
155 | 150 |
devel/upload.in \ |
156 | 151 |
$(docrst) \ |
157 | 152 |
$(docdot) \ |
158 |
$(docsgml) \ |
|
159 | 153 |
doc/build-rapi-resources-doc \ |
160 | 154 |
doc/examples/bash_completion.in \ |
161 | 155 |
doc/examples/ganeti.initd.in \ |
... | ... | |
228 | 222 |
|
229 | 223 |
TESTS_ENVIRONMENT = PYTHONPATH=.:$(top_builddir) |
230 | 224 |
|
225 |
RAPI_RESOURCES = $(wildcard lib/rapi/*.py) |
|
231 | 226 |
|
232 | 227 |
all-local: stamp-directories lib/_autoconf.py devel/upload \ |
233 | 228 |
doc/examples/bash_completion \ |
... | ... | |
250 | 245 |
doc/%.pdf: doc/%.in $(DOCBOOK_WRAPPER) |
251 | 246 |
$(DOCBOOK_WRAPPER) "$(DOCBOOK2PDF)" $< $@ |
252 | 247 |
|
253 |
doc/%.html: doc/%.in $(DOCBOOK_WRAPPER) |
|
254 |
$(DOCBOOK_WRAPPER) "$(DOCBOOK2HTML) --nochunks" $< $@ |
|
255 |
|
|
256 | 248 |
doc/%.html: doc/%.rst |
257 | 249 |
$(RST2HTML) $< $@ |
258 | 250 |
|
... | ... | |
261 | 253 |
|
262 | 254 |
doc/design-2.0.html: doc/design-2.0.rst doc/arch-2.0.png |
263 | 255 |
|
264 |
doc/rapi.pdf doc/rapi.html doc/rapi.in: doc/rapi-resources.sgml
|
|
256 |
doc/rapi.pdf doc/rapi.html: doc/rapi-resources.gen
|
|
265 | 257 |
|
266 |
doc/rapi-resources.sgml: $(BUILD_RAPI_RESOURCE_DOC) lib/rapi/connector.py |
|
267 |
PYTHONPATH=.:$(top_builddir) $(BUILD_RAPI_RESOURCE_DOC) > $@ || rm -f $@ |
|
258 |
doc/rapi-resources.gen: $(BUILD_RAPI_RESOURCE_DOC) $(RAPI_RESOURCES) |
|
259 |
PYTHONPATH=.:$(top_builddir) $(BUILD_RAPI_RESOURCE_DOC) > $@ || \ |
|
260 |
rm -f $@ |
|
268 | 261 |
|
269 | 262 |
man/%.7: man/%.in man/footer.sgml $(DOCBOOK_WRAPPER) |
270 | 263 |
$(DOCBOOK_WRAPPER) "$(DOCBOOK2MAN)" $< $@ |
... | ... | |
322 | 315 |
echo 's#@CUSTOM_XEN_KERNEL@#$(XEN_KERNEL)#g'; \ |
323 | 316 |
echo 's#@CUSTOM_XEN_INITRD@#$(XEN_INITRD)#g'; \ |
324 | 317 |
echo 's#@RPL_FILE_STORAGE_DIR@#$(FILE_STORAGE_DIR)#g'; \ |
325 |
echo '/@INCLUDE_RAPI_RESOURCES@/ {'; \ |
|
326 |
echo ' r $(abs_top_builddir)/doc/rapi-resources.sgml'; \ |
|
327 |
echo ' d'; \ |
|
328 |
echo '}'; \ |
|
329 | 318 |
} > $@ |
330 | 319 |
|
331 | 320 |
# We need to create symlinks because "make distcheck" will not install Python |
b/doc/build-rapi-resources-doc | ||
---|---|---|
20 | 20 |
|
21 | 21 |
"""Script to generate documentation for remote API resources. |
22 | 22 |
|
23 |
This is hard-coded to the section numbering we have in the master RST |
|
24 |
document. |
|
25 |
|
|
23 | 26 |
""" |
24 | 27 |
|
25 | 28 |
import re |
... | ... | |
33 | 36 |
CHECKED_COMMANDS = ["GET", "POST", "PUT", "DELETE"] |
34 | 37 |
|
35 | 38 |
|
39 |
def beautify(text): |
|
40 |
"""A couple of small enhancements, epydoc-to-rst. |
|
41 |
|
|
42 |
""" |
|
43 |
pairs = [ |
|
44 |
("@return:", "Returns:"), |
|
45 |
] |
|
46 |
|
|
47 |
for old, new in pairs: |
|
48 |
text = text.replace(old, new) |
|
49 |
|
|
50 |
return text |
|
51 |
|
|
52 |
|
|
53 |
def indent(text): |
|
54 |
"""Returns a text block with all lines indented. |
|
55 |
|
|
56 |
""" |
|
57 |
lines = text.splitlines() |
|
58 |
lines = [" " + l for l in lines] |
|
59 |
return "\n".join(lines) |
|
60 |
|
|
61 |
|
|
36 | 62 |
def main(): |
37 | 63 |
# Get list of all resources |
38 | 64 |
all = list(connector.CONNECTOR.itervalues()) |
... | ... | |
40 | 66 |
# Sort rlib by URI |
41 | 67 |
all.sort(cmp=lambda a, b: cmp(a.DOC_URI, b.DOC_URI)) |
42 | 68 |
|
43 |
print "<!-- Automatically generated, do not edit -->"
|
|
69 |
print ".. Automatically generated, do not edit\n"
|
|
44 | 70 |
|
45 | 71 |
for cls in all: |
46 |
print "<sect2>"
|
|
47 |
print "<title>%s</title>" % cgi.escape(cls.DOC_URI)
|
|
72 |
title = cls.DOC_URI
|
|
73 |
print "%s\n%s\n" % (title, "+" * len(title))
|
|
48 | 74 |
|
49 | 75 |
# Class docstring |
50 | 76 |
description = inspect.getdoc(cls) |
51 | 77 |
if description: |
52 |
print ("<literallayout>%s</literallayout>" % |
|
53 |
cgi.escape(description.strip())) |
|
54 |
|
|
55 |
print '<informaltable><tgroup cols="2">' |
|
56 |
print '<colspec colwidth="1*">' |
|
57 |
print '<colspec colwidth="5*">' |
|
58 |
print "<thead>" |
|
59 |
print " <row>" |
|
60 |
print " <entry>Method</entry>" |
|
61 |
print " <entry>Description</entry>" |
|
62 |
print " </row>" |
|
63 |
print "</thead>" |
|
64 |
print '<tbody valign="top">' |
|
78 |
print "::\n" |
|
79 |
print indent(description.strip()) |
|
80 |
|
|
81 |
supported = [cmd for cmd in CHECKED_COMMANDS if hasattr(cls, cmd)] |
|
82 |
print "It supports the following commands: %s." % (", ".join(supported)) |
|
83 |
|
|
65 | 84 |
|
66 | 85 |
for cmd in CHECKED_COMMANDS: |
67 | 86 |
if not hasattr(cls, cmd): |
68 | 87 |
continue |
69 | 88 |
|
89 |
print "%s\n%s\n" % (cmd, "~" * len(cmd)) |
|
90 |
|
|
70 | 91 |
# Get docstring |
71 | 92 |
text = inspect.getdoc(getattr(cls, cmd)) |
72 |
if not text: |
|
73 |
text = "" |
|
74 |
|
|
75 |
print "<row>" |
|
76 |
print " <entry>%s</entry>" % cgi.escape(cmd) |
|
77 |
print (" <entry><literallayout>%s</literallayout></entry>" % |
|
78 |
cgi.escape(text.strip())) |
|
79 |
print "</row>" |
|
80 |
|
|
81 |
print "</tbody>" |
|
82 |
print "</tgroup></informaltable>" |
|
83 |
|
|
84 |
print "</sect2>" |
|
93 |
if text: |
|
94 |
text = beautify(text) |
|
95 |
print "::\n" |
|
96 |
print indent(text) |
|
97 |
|
|
85 | 98 |
|
86 | 99 |
|
87 | 100 |
if __name__ == "__main__": |
b/doc/rapi.rst | ||
---|---|---|
1 |
Ganeti remote API |
|
2 |
================= |
|
3 |
|
|
4 |
Documents Ganeti version 2.0 |
|
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 |
Protocol |
|
20 |
-------- |
|
21 |
|
|
22 |
The protocol used is JSON_ over HTTP designed after the REST_ |
|
23 |
principle. |
|
24 |
|
|
25 |
.. _JSON: http://www.json.org/ |
|
26 |
.. _REST: http://en.wikipedia.org/wiki/Representational_State_Transfer |
|
27 |
|
|
28 |
Usage examples |
|
29 |
-------------- |
|
30 |
|
|
31 |
You can access the API using your favorite programming language as |
|
32 |
long as it supports network connections. |
|
33 |
|
|
34 |
Shell |
|
35 |
+++++ |
|
36 |
|
|
37 |
Using wget:: |
|
38 |
|
|
39 |
wget -q -O - https://CLUSTERNAME:5080/2/info |
|
40 |
|
|
41 |
or curl:: |
|
42 |
|
|
43 |
curl https://CLUSTERNAME:5080/2/info |
|
44 |
|
|
45 |
|
|
46 |
Python |
|
47 |
++++++ |
|
48 |
|
|
49 |
:: |
|
50 |
|
|
51 |
import urllib2 |
|
52 |
f = urllib2.urlopen('https://CLUSTERNAME:5080/info') |
|
53 |
print f.read() |
|
54 |
|
|
55 |
|
|
56 |
JavaScript |
|
57 |
++++++++++ |
|
58 |
|
|
59 |
.. warning:: While it's possible to use JavaScript, it poses several potential |
|
60 |
problems, including browser blocking request due to |
|
61 |
non-standard ports or different domain names. Fetching the data |
|
62 |
on the webserver is easier. |
|
63 |
|
|
64 |
:: |
|
65 |
|
|
66 |
var url = 'https://CLUSTERNAME:5080/info'; |
|
67 |
var info; |
|
68 |
var xmlreq = new XMLHttpRequest(); |
|
69 |
xmlreq.onreadystatechange = function () { |
|
70 |
if (xmlreq.readyState != 4) return; |
|
71 |
if (xmlreq.status == 200) { |
|
72 |
info = eval("(" + xmlreq.responseText + ")"); |
|
73 |
alert(info); |
|
74 |
} else { |
|
75 |
alert('Error fetching cluster info'); |
|
76 |
} |
|
77 |
xmlreq = null; |
|
78 |
}; |
|
79 |
xmlreq.open('GET', url, true); |
|
80 |
xmlreq.send(null); |
|
81 |
|
|
82 |
Resources |
|
83 |
--------- |
|
84 |
|
|
85 |
.. include:: rapi-resources.gen |
/dev/null | ||
---|---|---|
1 |
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.2//EN" [ |
|
2 |
<!ENTITY JsonLink "http://www.json.org/"> |
|
3 |
<!ENTITY WikipediaRESTLink |
|
4 |
"http://en.wikipedia.org/wiki/Representational_State_Transfer"> |
|
5 |
]> |
|
6 |
<article class="specification"> |
|
7 |
<articleinfo> |
|
8 |
<title>Ganeti remote API</title> |
|
9 |
</articleinfo> |
|
10 |
|
|
11 |
<para>Documents Ganeti version 2.0</para> |
|
12 |
|
|
13 |
<sect1> |
|
14 |
<title>Introduction</title> |
|
15 |
|
|
16 |
<para>Ganeti supports a remote API for enable external tools to |
|
17 |
easily retrieve information about a cluster's state. The remote API |
|
18 |
daemon, <command>ganeti-rapi</command>, is automatically started on |
|
19 |
the master node. By default it runs on TCP port 5080, but this can |
|
20 |
be changed either in <filename>…/constants.py</filename> or |
|
21 |
via the command line parameter <option>-p</option>. SSL support can |
|
22 |
also be enabled by passing command line parameters.</para> |
|
23 |
|
|
24 |
</sect1> |
|
25 |
|
|
26 |
<sect1> |
|
27 |
<title>Protocol</title> |
|
28 |
|
|
29 |
<para>The protocol used is <ulink url="&JsonLink;">JSON</ulink> over HTTP |
|
30 |
designed after the <ulink url="&WikipediaRESTLink;">REST</ulink> principle. |
|
31 |
</para> |
|
32 |
</sect1> |
|
33 |
|
|
34 |
<sect1> |
|
35 |
<title>Usage examples</title> |
|
36 |
|
|
37 |
<para>You can access the API using your favorite programming language as long |
|
38 |
as it supports network connections.</para> |
|
39 |
|
|
40 |
<sect2> |
|
41 |
<title>Shell</title> |
|
42 |
<screen>wget -q -O - https://<replaceable>CLUSTERNAME</replaceable>:5080/2/info</screen> |
|
43 |
<para>or</para> |
|
44 |
<screen>curl https://<replaceable>CLUSTERNAME</replaceable>:5080/2/info</screen> |
|
45 |
</sect2> |
|
46 |
|
|
47 |
<sect2> |
|
48 |
<title>Python</title> |
|
49 |
<screen>import urllib2 |
|
50 |
f = urllib2.urlopen('https://<replaceable>CLUSTERNAME</replaceable>:5080/info') |
|
51 |
print f.read()</screen> |
|
52 |
</sect2> |
|
53 |
|
|
54 |
<sect2> |
|
55 |
<title>JavaScript</title> |
|
56 |
<note> |
|
57 |
<para>While it's possible to use JavaScript, it poses several potential |
|
58 |
problems, including browser blocking request due to non-standard ports |
|
59 |
or different domain names. Fetching the data on the webserver is |
|
60 |
easier.</para> |
|
61 |
</note> |
|
62 |
<screen>var url = 'https://<replaceable>CLUSTERNAME</replaceable>:5080/info'; |
|
63 |
var info; |
|
64 |
|
|
65 |
var xmlreq = new XMLHttpRequest(); |
|
66 |
xmlreq.onreadystatechange = function () { |
|
67 |
if (xmlreq.readyState != 4) return; |
|
68 |
if (xmlreq.status == 200) { |
|
69 |
info = eval("(" + xmlreq.responseText + ")"); |
|
70 |
alert(info); |
|
71 |
} else { |
|
72 |
alert('Error fetching cluster info'); |
|
73 |
} |
|
74 |
xmlreq = null; |
|
75 |
}; |
|
76 |
xmlreq.open('GET', url, true); |
|
77 |
xmlreq.send(null);</screen> |
|
78 |
</sect2> |
|
79 |
|
|
80 |
</sect1> |
|
81 |
|
|
82 |
<sect1> |
|
83 |
<title>Resources</title> |
|
84 |
@INCLUDE_RAPI_RESOURCES@ |
|
85 |
</sect1> |
|
86 |
|
|
87 |
</article> |
Also available in: Unified diff