From 36d6df671e57cd755dc4b0b38acd539615b73936 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 30 May 2013 12:31:54 +0100 Subject: [PATCH] tests/http: Add a test of HTTP protocol. Disabled for various reasons. See comment in Makefile.am. --- Makefile.am | 1 + configure.ac | 1 + tests/http/Makefile.am | 41 +++++++++ tests/http/test-http.py | 190 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 tests/http/Makefile.am create mode 100755 tests/http/test-http.py diff --git a/Makefile.am b/Makefile.am index 9726667b5..0832e6e65 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,6 +57,7 @@ SUBDIRS += tests/bigdirs SUBDIRS += tests/disk-labels SUBDIRS += tests/hotplug SUBDIRS += tests/nbd +SUBDIRS += tests/http SUBDIRS += tests/syslinux SUBDIRS += tests/regressions endif diff --git a/configure.ac b/configure.ac index 30af87140..12c4bed2b 100644 --- a/configure.ac +++ b/configure.ac @@ -1629,6 +1629,7 @@ AC_CONFIG_FILES([Makefile tests/guests/Makefile tests/guests/guests.xml tests/hotplug/Makefile + tests/http/Makefile tests/luks/Makefile tests/lvm/Makefile tests/md/Makefile diff --git a/tests/http/Makefile.am b/tests/http/Makefile.am new file mode 100644 index 000000000..580714b46 --- /dev/null +++ b/tests/http/Makefile.am @@ -0,0 +1,41 @@ +# libguestfs +# Copyright (C) 2013 Red Hat 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. + +include $(top_srcdir)/subdir-rules.mk + +# Test uses the Python SimpleHTTPServer module which is +# conveniently part of Python core. + +if HAVE_PYTHON + +# Test disabled for now. Several things conspire to stop this working: +# +# (1) Python may throw "Broken pipe" errors: +# http://bugs.python.org/issue14574 +# Fixed upstream in Python >= 2.7.5. +# +# (2) Fedora 18's Curl is broken. Can be resolved by compiling Curl +# from git and setting LD_LIBRARY_PATH=~/curl/lib/.libs + +# TESTS = test-http.py +# +# TESTS_ENVIRONMENT = $(top_builddir)/run --test $(PYTHON) + +endif + +EXTRA_DIST = \ + test-http.py diff --git a/tests/http/test-http.py b/tests/http/test-http.py new file mode 100755 index 000000000..02401807d --- /dev/null +++ b/tests/http/test-http.py @@ -0,0 +1,190 @@ +# -*- python -*- +# libguestfs +# Copyright (C) 2013 Red Hat 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. + +import sys +import os +import re +import shutil +from time import sleep +from random import randint + +progname = os.path.basename(sys.argv[0]) +guestsdir = "../guests" +listen_addr = "localhost" +#listen_addr = "127.0.0.1" +#listen_addr = "" +connect_addr = "localhost" +#connect_addr = "127.0.0.1" + +if os.getenv ('SKIP_TEST_HTTP_PY'): + print >>sys.stderr, \ + ("%s: test skipped because environment variable is set" % progname) + exit (77) + +# Proxy settings can break this test. +del os.environ['http_proxy'] + +# Remove the stamp file. +stampfile = "%s/stamp-test-http" % os.getcwd() +def unlink_stampfile (): + try: + os.unlink (stampfile) + except: + pass +unlink_stampfile () + +# Choose a random port number. +# XXX Should check it is not in use. +port = randint (60000, 65000) + +pid = os.fork () +if pid > 0: + # Parent (client). + import guestfs + + # Make sure that the child (HTTP server) is killed on exit even if + # we exit abnormally. + def cleanup (): + unlink_stampfile () + if pid > 0: + os.kill (pid, 15) + sys.exitfunc = cleanup + + # Wait for the child to touch the stamp file to indicate it has + # started listening for requests. + for i in range (1, 10): + if os.access (stampfile, os.F_OK): + break + sleep (1) + if i == 3: + print ("%s: waiting for the web server to start up ..." % progname) + if not os.access (stampfile, os.F_OK): + print >>sys.stderr, \ + ("%s: error: web server process did not start up" % progname) + exit (1) + + # Create libguestfs handle and connect to the web server. + g = guestfs.GuestFS (python_return_dict=True) + server = "%s:%d" % (connect_addr, port) + g.add_drive_opts ("/fedora.img", readonly=True, format="raw", + protocol="http", server=[server]) + g.launch () + + # Inspection is quite a thorough test. + roots = g.inspect_os () + if len (roots) == 0: + print >>sys.stderr, \ + ("%s: error: inspection failed to find any OSes in guest image" % + progname) + exit (1) + if len (roots) > 1: + print >>sys.stderr, \ + ("%s: error: inspection found a multi-boot OS which is not expected" % + progname) + exit (1) + + type_ = g.inspect_get_type (roots[0]) + distro = g.inspect_get_distro (roots[0]) + if type_ != "linux" or distro != "fedora": + print >>sys.stderr, \ + ("%s: error: inspection found wrong OS type (%s, %s)" % + (progname, type_, distro)) + exit (1) + + g.close () + +else: + # Child (HTTP server). + from BaseHTTPServer import HTTPServer + from SimpleHTTPServer import SimpleHTTPRequestHandler + from SocketServer import ThreadingMixIn + + os.chdir (guestsdir) + + class ThreadingServer (ThreadingMixIn, HTTPServer): + pass + + # This is an extended version of SimpleHTTPRequestHandler that can + # handle byte ranges. See also: + # https://naclports.googlecode.com/svn/trunk/src/httpd.py + class ByteRangeRequestHandler (SimpleHTTPRequestHandler): + def do_GET (self): + if 'Range' in self.headers: + m = re.match ('\s*bytes\s*=\s*(\d+)\s*-\s*(\d+)\s*', + self.headers['Range']) + if m: + start = int (m.group (1)) + end = int (m.group (2)) + length = end - start + 1 + f = self.send_head_partial (start, length) + if f: + f.seek (start, os.SEEK_CUR) + shutil.copyfileobj (f, self.wfile, length) + f.close () + return + + return SimpleHTTPRequestHandler.do_GET (self) + + def send_head_partial (self, offset, length): + path = self.translate_path(self.path) + f = None + if os.path.isdir (path): + if not self.path.endswith ('/'): + # redirect browser - doing basically what apache does + self.send_response (301) + self.send_header ("Location", self.path + "/") + self.end_headers () + return None + for index in "index.html", "index.htm": + index = os.path.join (path, index) + if os.path.exists (index): + path = index + break + else: + return self.list_directory (path) + ctype = self.guess_type (path) + try: + f = open (path, 'rb') + except IOError: + self.send_error (404, "File not found") + return None + self.send_response (206, 'Partial content') + self.send_header ("Content-Range", str(offset) + '-' + + str(length+offset-1)) + self.send_header ("Content-Length", str(length)) + self.send_header ("Content-type", ctype) + fs = os.fstat (f.fileno()) + self.send_header ("Last-Modified", + self.date_time_string(fs.st_mtime)) + self.end_headers() + return f + + server_address = (listen_addr, port) + httpd = ThreadingServer (server_address, ByteRangeRequestHandler) + + sa = httpd.socket.getsockname () + print ("%s: serving %s on %s port %d ..." % (progname, + os.getcwd(), sa[0], sa[1])) + + # Touch the stamp file, which starts the client. + open (stampfile, 'a') + + # Start serving until killed. + httpd.serve_forever () + + os._exit (0)