Commit 8c229cc7 authored by Oleksiy Mishchenko's avatar Oleksiy Mishchenko

Initial copy of RAPI filebase to the trunk

Reviewed-by: iustinp
parent 0ed468d3
......@@ -14,6 +14,7 @@ DOCBOOK_WRAPPER = $(top_srcdir)/autotools/docbook-wrapper
REPLACE_VARS_SED = autotools/replace_vars.sed
hypervisordir = $(pkgpythondir)/hypervisor
rapidir = $(pkgpythondir)/rapi
toolsdir = $(pkglibdir)/tools
docdir = $(datadir)/doc/$(PACKAGE)
......@@ -25,6 +26,7 @@ DIRS = \
doc/examples \
lib \
lib/hypervisor \
lib/rapi \
man \
qa \
qa/hooks \
......@@ -43,6 +45,7 @@ CLEANFILES = \
doc/examples/ganeti.cron \
lib/*.py[co] \
lib/hypervisor/*.py[co] \
lib/rapi/*.py[co] \
man/*.[78] \
man/*.in \
qa/*.py[co] \
......@@ -84,6 +87,13 @@ hypervisor_PYTHON = \
lib/hypervisor/hv_fake.py \
lib/hypervisor/hv_xen.py
rapi_PYTHON = \
lib/rapi/__init__.py \
lib/rapi/RESTHTTPServer.py \
lib/rapi/httperror.py \
lib/rapi/resources.py
docsgml = \
doc/hooks.sgml \
doc/install.sgml \
......@@ -99,6 +109,7 @@ dist_sbin_SCRIPTS = \
daemons/ganeti-watcher \
daemons/ganeti-master \
daemons/ganeti-masterd \
daemons/ganeti-rapi \
scripts/gnt-backup \
scripts/gnt-cluster \
scripts/gnt-debug \
......@@ -254,7 +265,7 @@ $(REPLACE_VARS_SED): Makefile stamp-directories
#.PHONY: srclinks
srclinks: stamp-directories
set -e; \
for i in man/footer.sgml $(pkgpython_PYTHON) $(hypervisor_PYTHON); do \
for i in man/footer.sgml $(pkgpython_PYTHON) $(hypervisor_PYTHON) $(rapi_PYTHON); do \
if test ! -f $$i -a -f $(abs_top_srcdir)/$$i; then \
$(LN_S) $(abs_top_srcdir)/$$i $$i; \
fi; \
......
#!/usr/bin/python
#
# Copyright (C) 2006, 2007 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.
""" Ganeti Remote API master script.
"""
import glob
import optparse
import sys
import os
# we need to import rpc early in order to get our custom reactor,
# instead of the default twisted one; without this, things breaks in a
# not-nice-to-debug way
from ganeti import rpc
from ganeti import constants
from ganeti import utils
from ganeti.rapi import RESTHTTPServer
def ParseOptions():
"""Parse the command line options.
Returns:
(options, args) as from OptionParser.parse_args()
"""
parser = optparse.OptionParser(description="Ganeti Remote API",
usage="%prog [-d] [-p port]",
version="%%prog (ganeti) %s" %
constants.RAPI_VERSION)
parser.add_option("-d", "--debug", dest="debug",
help="Enable some debug messages",
default=False, action="store_true")
parser.add_option("-p", "--port", dest="port",
help="Port to run API (%s default)." %
constants.RAPI_PORT,
default=constants.RAPI_PORT, type="int")
parser.add_option("-S", "--https", dest="ssl",
help="Secure HTTP protocol with SSL",
default=False, action="store_true")
parser.add_option("-K", "--ssl-key", dest="ssl_key",
help="SSL key",
default=None, type="string")
parser.add_option("-C", "--ssl-cert", dest="ssl_cert",
help="SSL certificate",
default=None, type="string")
parser.add_option("-f", "--foreground", dest="fork",
help="Don't detach from the current terminal",
default=True, action="store_false")
options, args = parser.parse_args()
if len(args) != 0:
print >> sys.stderr, "Usage: %s [-d] [-p port]" % sys.argv[0]
sys.exit(1)
if options.ssl and not (options.ssl_cert and options.ssl_key):
print >> sys.stderr, ("For secure mode please provide "
"--ssl-key and --ssl-cert arguments")
sys.exit(1)
return options, args
def main():
"""Main function.
"""
options, args = ParseOptions()
if options.fork:
utils.Daemonize(logfile=constants.LOG_RAPISERVER)
RESTHTTPServer.start(options)
sys.exit(0)
if __name__ == '__main__':
main()
#
#
# 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.
"""RESTfull HTTPS Server module.
"""
import socket
import BaseHTTPServer
import OpenSSL
import time
from ganeti import constants
from ganeti import errors
from ganeti import logger
from ganeti import rpc
from ganeti import serializer
from ganeti.rapi import resources
from ganeti.rapi import httperror
class HttpLogfile:
"""Utility class to write HTTP server log files.
The written format is the "Common Log Format" as defined by Apache:
http://httpd.apache.org/docs/2.2/mod/mod_log_config.html#examples
"""
MONTHNAME = [None,
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
def __init__(self, path):
self._fd = open(path, 'a', 1)
def __del__(self):
try:
self.Close()
except:
# Swallow exceptions
pass
def Close(self):
if self._fd is not None:
self._fd.close()
self._fd = None
def LogRequest(self, request, format, *args):
if self._fd is None:
raise errors.ProgrammerError("Logfile already closed")
request_time = self._FormatCurrentTime()
self._fd.write("%s %s %s [%s] %s\n" % (
# Remote host address
request.address_string(),
# RFC1413 identity (identd)
"-",
# Remote user
"-",
# Request time
request_time,
# Message
format % args,
))
def _FormatCurrentTime(self):
"""Formats current time in Common Log Format.
"""
return self._FormatLogTime(time.time())
def _FormatLogTime(self, seconds):
"""Formats time for Common Log Format.
All timestamps are logged in the UTC timezone.
Args:
- seconds: Time in seconds since the epoch
"""
(_, month, _, _, _, _, _, _, _) = tm = time.gmtime(seconds)
format = "%d/" + self.MONTHNAME[month] + "/%Y:%H:%M:%S +0000"
return time.strftime(format, tm)
class RESTHTTPServer(BaseHTTPServer.HTTPServer):
"""Class to provide an HTTP/HTTPS server.
"""
allow_reuse_address = True
def __init__(self, server_address, HandlerClass, options):
"""REST Server Constructor.
Args:
server_address: a touple containing:
ip: a string with IP address, localhost if empty string
port: port number, integer
HandlerClass: HTTPRequestHandler object
options: Command-line options
"""
logger.SetupLogging(debug=options.debug, program='ganeti-rapi')
self.httplog = HttpLogfile(constants.LOG_RAPIACCESS)
BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
if options.ssl:
# Set up SSL
context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
context.use_privatekey_file(options.ssl_key)
context.use_certificate_file(options.ssl_cert)
self.socket = OpenSSL.SSL.Connection(context,
socket.socket(self.address_family,
self.socket_type))
else:
self.socket = socket.socket(self.address_family, self.socket_type)
self.server_bind()
self.server_activate()
class JsonResponse:
CONTENT_TYPE = "application/json"
def Encode(self, data):
return serializer.DumpJson(data)
class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""REST Request Handler Class.
"""
def setup(self):
"""Setup secure read and write file objects.
"""
self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
self._resmap = resources.Mapper()
def handle_one_request(self):
"""Handle a single REST request.
"""
self.raw_requestline = None
try:
self.raw_requestline = self.rfile.readline()
except OpenSSL.SSL.Error, ex:
logger.Error("Error in SSL: %s" % str(ex))
if not self.raw_requestline:
self.close_connection = 1
return
if not self.parse_request(): # An error code has been sent, just exit
return
try:
(HandlerClass, items, args) = self._resmap.getController(self.path)
handler = HandlerClass(self, items, args)
command = self.command.upper()
try:
fn = getattr(handler, command)
except AttributeError, err:
raise httperror.HTTPBadRequest()
try:
result = fn()
except errors.OpPrereqError, err:
# TODO: "Not found" is not always the correct error. Ganeti's core must
# differentiate between different error types.
raise httperror.HTTPNotFound(message=str(err))
encoder = JsonResponse()
encoded_result = encoder.Encode(result)
self.send_response(200)
self.send_header("Content-Type", encoder.CONTENT_TYPE)
self.end_headers()
self.wfile.write(encoded_result)
except httperror.HTTPException, err:
self.send_error(err.code, message=err.message)
except Exception, err:
self.send_error(httperror.HTTPInternalError.code, message=str(err))
def log_message(self, format, *args):
"""Log an arbitrary message.
This is used by all other logging functions.
The first argument, FORMAT, is a format string for the
message to be logged. If the format string contains
any % escapes requiring parameters, they should be
specified as subsequent arguments (it's just like
printf!).
"""
self.server.httplog.LogRequest(self, format, *args)
def start(options):
# Disable signal handlers, otherwise we can't exit the daemon in a clean way
# by sending a signal.
rpc.install_twisted_signal_handlers = False
httpd = RESTHTTPServer(("", options.port), RESTRequestHandler, options)
try:
httpd.serve_forever()
finally:
httpd.server_close()
#
#
# 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.
# empty file for package definition
#
#
# Copyright (C) 2006, 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.
"""HTTP errors.
"""
class HTTPException(Exception):
code = None
message = None
def __init__(self, message=None):
if message is not None:
self.message = message
class HTTPBadRequest(HTTPException):
code = 400
class HTTPNotFound(HTTPException):
code = 404
class HTTPInternalError(HTTPException):
code = 500
class HTTPServiceUnavailable(HTTPException):
code = 503
This diff is collapsed.
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