From 4cbd446279b16f68a192bafe5bca0f2fe4c01a4e Mon Sep 17 00:00:00 2001 From: Oleksiy Mishchenko Date: Fri, 11 Jul 2008 13:48:11 +0000 Subject: [PATCH] Copy the rest of the Restful-API files to trunk Reviewed-by: imsnah --- Makefile.am | 12 ++- doc/build-rapi-resources-doc | 87 +++++++++++++++ doc/rapi.sgml | 94 ++++++++++++++++ test/ganeti.rapi.resources_unittest.py | 144 +++++++++++++++++++++++++ 4 files changed, 336 insertions(+), 1 deletion(-) create mode 100755 doc/build-rapi-resources-doc create mode 100644 doc/rapi.sgml create mode 100755 test/ganeti.rapi.resources_unittest.py diff --git a/Makefile.am b/Makefile.am index 7b8bae0b3..1156730d4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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; \ diff --git a/doc/build-rapi-resources-doc b/doc/build-rapi-resources-doc new file mode 100755 index 000000000..c73e51883 --- /dev/null +++ b/doc/build-rapi-resources-doc @@ -0,0 +1,87 @@ +#!/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 "" + + for cls in all: + print "" + print "%s" % cgi.escape(cls.DOC_URI) + + # Class docstring + description = inspect.getdoc(cls) + if description: + print ("%s" % + cgi.escape(description.strip())) + + print '' + print '' + print '' + print "" + print " " + print " Method" + print " Description" + print " " + print "" + print '' + + for cmd in CHECKED_COMMANDS: + if not hasattr(cls, cmd): + continue + + # Get docstring + text = inspect.getdoc(getattr(cls, cmd)) + if not text: + text = "" + + print "" + print " %s" % cgi.escape(cmd) + print (" %s" % + cgi.escape(text.strip())) + print "" + + print "" + print "" + + print "" + + +if __name__ == "__main__": + main() diff --git a/doc/rapi.sgml b/doc/rapi.sgml new file mode 100644 index 000000000..51bf56e4f --- /dev/null +++ b/doc/rapi.sgml @@ -0,0 +1,94 @@ + + + +]> +
+ + Ganeti remote API + + +Documents Ganeti version 1.2 + + + Introduction + + Ganeti supports a remote API for enable external tools to easily + retrieve information about a cluster's state. The remote API daemon, + ganeti-rapi, is automatically started on + the master node if the --enable-rapi + parameter is passed to the configure + script. Alternatively you can start it manually. By default it runs on TCP + port 5080, but this can be changed either in + …/constants.py or via the command line + parameter -p. SSL support can also be + enabled by passing command line parameters. + + + 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. + + + + + Protocol + + The protocol used is JSON over HTTP + designed after the REST principle. + + + + + Usage examples + + You can access the API using your favorite programming language as long + as it supports network connections. + + + Shell + wget -q -O - http://CLUSTERNAME:5080/info + + + + Python + import urllib2 +f = urllib2.urlopen('http://CLUSTERNAME:5080/info') +print f.read() + + + + JavaScript + + 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. + + var url = 'http://CLUSTERNAME: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); + + + + + + Resources + &IncludeResources; + + +
diff --git a/test/ganeti.rapi.resources_unittest.py b/test/ganeti.rapi.resources_unittest.py new file mode 100755 index 000000000..8fc40eee7 --- /dev/null +++ b/test/ganeti.rapi.resources_unittest.py @@ -0,0 +1,144 @@ +#!/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() -- GitLab