Commit 4cbd4462 authored by Oleksiy Mishchenko's avatar Oleksiy Mishchenko

Copy the rest of the Restful-API files to trunk

Reviewed-by: imsnah
parent df4c2628
......@@ -99,6 +99,7 @@ docsgml = \
doc/hooks.sgml \
doc/install.sgml \
doc/admin.sgml \
doc/rapi.sgml \
doc/iallocator.sgml
doc_DATA = \
......@@ -131,6 +132,7 @@ EXTRA_DIST = \
autotools/docbook-wrapper \
devel/upload.in \
$(docsgml) \
doc/build-rapi-resources-doc \
doc/examples/ganeti.initd.in \
doc/examples/ganeti.cron.in \
doc/examples/dumb-allocator \
......@@ -186,6 +188,7 @@ dist_TESTS = \
test/ganeti.locking_unittest.py \
test/ganeti.serializer_unittest.py \
test/ganeti.workerpool_unittest.py \
test/ganeti.rapi.resources_unittest.py \
test/ganeti.constants_unittest.py
nodist_TESTS =
......@@ -218,6 +221,13 @@ doc/%.pdf: doc/%.in $(DOCBOOK_WRAPPER)
doc/%.html: doc/%.in $(DOCBOOK_WRAPPER)
$(DOCBOOK_WRAPPER) $< $@
doc/rapi.pdf doc/rapi.html: doc/rapi-resources.sgml
doc/rapi-resources.sgml: doc/build-rapi-resources-doc lib/rapi/resources.py
PYTHONPATH=.:$(top_builddir) $< > $@ || rm -f $@
.INTERMEDIATE: doc/rapi-resources.sgml
man/%.7: man/%.in man/footer.sgml $(DOCBOOK_WRAPPER)
$(DOCBOOK_WRAPPER) $< $@
......@@ -226,7 +236,7 @@ man/%.8: man/%.in man/footer.sgml $(DOCBOOK_WRAPPER)
man/footer.sgml $(TESTS): srclinks
$(TESTS): ganeti
$(TESTS) doc/build-rapi-resources-doc: ganeti lib/_autoconf.py
lib/_autoconf.py: Makefile stamp-directories
set -e; \
......
#!/usr/bin/python
#
# Copyright (C) 2008 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Script to generate documentation for remote API resources.
"""
import re
import cgi
import inspect
from ganeti.rapi import resources
CHECKED_COMMANDS = ["GET", "POST", "PUT", "DELETE"]
def main():
# Get list of all resources
all = list(resources._CONNECTOR.itervalues())
# Sort resources by URI
all.sort(cmp=lambda a, b: cmp(a.DOC_URI, b.DOC_URI))
print "<!-- Automatically generated, do not edit -->"
for cls in all:
print "<sect2>"
print "<title>%s</title>" % cgi.escape(cls.DOC_URI)
# Class docstring
description = inspect.getdoc(cls)
if description:
print ("<literallayout>%s</literallayout>" %
cgi.escape(description.strip()))
print '<informaltable><tgroup cols="2">'
print '<colspec colwidth="1*">'
print '<colspec colwidth="5*">'
print "<thead>"
print " <row>"
print " <entry>Method</entry>"
print " <entry>Description</entry>"
print " </row>"
print "</thead>"
print '<tbody valign="top">'
for cmd in CHECKED_COMMANDS:
if not hasattr(cls, cmd):
continue
# Get docstring
text = inspect.getdoc(getattr(cls, cmd))
if not text:
text = ""
print "<row>"
print " <entry>%s</entry>" % cgi.escape(cmd)
print (" <entry><literallayout>%s</literallayout></entry>" %
cgi.escape(text.strip()))
print "</row>"
print "</tbody>"
print "</tgroup></informaltable>"
print "</sect2>"
if __name__ == "__main__":
main()
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook V4.2//EN" [
<!ENTITY JsonLink "http://www.json.org/">
<!ENTITY WikipediaRESTLink
"http://en.wikipedia.org/wiki/Representational_State_Transfer">
<!ENTITY IncludeResources SYSTEM "doc/rapi-resources.sgml">
]>
<article class="specification">
<articleinfo>
<title>Ganeti remote API</title>
</articleinfo>
<para>Documents Ganeti version 1.2</para>
<sect1>
<title>Introduction</title>
<para>Ganeti supports a remote API for enable external tools to easily
retrieve information about a cluster's state. The remote API daemon,
<computeroutput>ganeti-rapi</computeroutput>, is automatically started on
the master node if the <computeroutput>--enable-rapi</computeroutput>
parameter is passed to the <computeroutput>configure</computeroutput>
script. Alternatively you can start it manually. By default it runs on TCP
port 5080, but this can be changed either in
<filename>&hellip;/constants.py</filename> or via the command line
parameter <computeroutput>-p</computeroutput>. SSL support can also be
enabled by passing command line parameters.</para>
<note>
<para>Ganeti 1.2 only supports a limited set of calls, all of them
read-only. The next major version will have support for write
operations.</para>
</note>
</sect1>
<sect1>
<title>Protocol</title>
<para>The protocol used is <ulink url="&JsonLink;">JSON</ulink> over HTTP
designed after the <ulink url="&WikipediaRESTLink;">REST</ulink> principle.
</para>
</sect1>
<sect1>
<title>Usage examples</title>
<para>You can access the API using your favorite programming language as long
as it supports network connections.</para>
<sect2>
<title>Shell</title>
<screen>wget -q -O - http://<replaceable>CLUSTERNAME</replaceable>:5080/info</screen>
</sect2>
<sect2>
<title>Python</title>
<screen>import urllib2
f = urllib2.urlopen('http://<replaceable>CLUSTERNAME</replaceable>:5080/info')
print f.read()</screen>
</sect2>
<sect2>
<title>JavaScript</title>
<note>
<para>While it's possible to use JavaScript, it poses several potential
problems, including browser blocking request due to non-standard ports
or different domain names. Fetching the data on the webserver is
easier.</para>
</note>
<screen>var url = 'http://<replaceable>CLUSTERNAME</replaceable>:5080/info';
var info;
var xmlreq = new XMLHttpRequest();
xmlreq.onreadystatechange = function () {
if (xmlreq.readyState != 4) return;
if (xmlreq.status == 200) {
info = eval("(" + xmlreq.responseText + ")");
alert(info);
} else {
alert('Error fetching cluster info');
}
xmlreq = null;
};
xmlreq.open('GET', url, true);
xmlreq.send(null);</screen>
</sect2>
</sect1>
<sect1>
<title>Resources</title>
&IncludeResources;
</sect1>
</article>
#!/usr/bin/python
#
# Copyright (C) 2007, 2008 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Script for unittesting the rapi.resources module"""
import os
import unittest
import tempfile
import time
from ganeti import errors
from ganeti.rapi import httperror
from ganeti.rapi import resources
from ganeti.rapi import RESTHTTPServer
class MapperTests(unittest.TestCase):
"""Tests for remote API URI mapper."""
def setUp(self):
self.map = resources.Mapper()
def _TestUri(self, uri, result):
self.assertEquals(self.map.getController(uri), result)
def _TestFailingUri(self, uri):
self.failUnlessRaises(httperror.HTTPNotFound, self.map.getController, uri)
def testMapper(self):
"""Testing resources.Mapper"""
self._TestUri("/tags", (resources.R_tags, [], {}))
self._TestUri('/instances/www.test.com',
(resources.R_instances_name,
['www.test.com'],
{}))
self._TestUri('/instances/www.test.com/tags?f=5&f=6&alt=html',
(resources.R_instances_name_tags,
['www.test.com'],
{'alt': ['html'],
'f': ['5', '6'],
}))
self._TestFailingUri("/tag")
self._TestFailingUri("/instances/does/not/exist")
class R_RootTests(unittest.TestCase):
"""Testing for R_root class."""
def setUp(self):
self.root = resources.R_root(None, None, None)
def testGet(self):
expected = [
{'name': 'info', 'uri': '/info'},
{'name': 'instances', 'uri': '/instances'},
{'name': 'nodes', 'uri': '/nodes'},
{'name': 'os', 'uri': '/os'},
{'name': 'tags', 'uri': '/tags'},
{'name': 'version', 'uri': '/version'},
]
self.assertEquals(self.root.GET(), expected)
class HttpLogfileTests(unittest.TestCase):
"""Rests for HttpLogfile class."""
class FakeRequest:
FAKE_ADDRESS = "1.2.3.4"
def address_string(self):
return self.FAKE_ADDRESS
def setUp(self):
self.tmpfile = tempfile.NamedTemporaryFile()
self.logfile = RESTHTTPServer.HttpLogfile(self.tmpfile.name)
def testFormatLogTime(self):
self._TestInTimezone(1208646123.0, "Europe/London",
"19/Apr/2008:23:02:03 +0000")
self._TestInTimezone(1208646123, "Europe/Zurich",
"19/Apr/2008:23:02:03 +0000")
self._TestInTimezone(1208646123, "Australia/Sydney",
"19/Apr/2008:23:02:03 +0000")
def _TestInTimezone(self, seconds, timezone, expected):
"""Tests HttpLogfile._FormatLogTime with a specific timezone
"""
# Preserve environment
old_TZ = os.environ.get("TZ", None)
try:
os.environ["TZ"] = timezone
time.tzset()
result = self.logfile._FormatLogTime(seconds)
finally:
# Restore environment
if old_TZ is not None:
os.environ["TZ"] = old_TZ
elif "TZ" in os.environ:
del os.environ["TZ"]
time.tzset()
self.assertEqual(result, expected)
def testClose(self):
self.logfile.Close()
def testCloseAndWrite(self):
request = self.FakeRequest()
self.logfile.Close()
self.assertRaises(errors.ProgrammerError, self.logfile.LogRequest,
request, "Message")
def testLogRequest(self):
request = self.FakeRequest()
self.logfile.LogRequest(request, "This is only a %s", "test")
self.logfile.Close()
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment