From 54aa265915f305f688051bebf940fcbe000ddce2 Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 28 May 2014 18:29:39 -0400 Subject: [PATCH] Add basic testing infrastructure make test will now run some sanity tests to make sure basic installation procedures work in a sinthetic test environment. Adds: - custom httpd setup for tests - use profiles to driver ipsilon servers and clients installation - starts multiple httpd servers This way we can test interaction between IDP and SP servers Signed-off-by: Simo Sorce --- Makefile | 13 ++ ipsilon/install/ipsilon-server-install | 3 + templates/install/idp.conf | 1 + tests/__init__.py | 0 tests/httpd.conf | 107 +++++++++++ tests/tests.py | 244 +++++++++++++++++++++++++ 6 files changed, 368 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/httpd.conf create mode 100755 tests/tests.py diff --git a/Makefile b/Makefile index d02a974..439caa9 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,19 @@ ui-node: less/ipsilon.less less/admin.less lessc --clean-css less/ipsilon.less ui/css/ipsilon.css lessc --clean-css less/admin.less ui/css/admin.css +clean: + rm -fr testdir + +lp-test: + pylint -d c,r,i,W0613 -r n -f colorized \ + --notes= \ + --ignored-classes=cherrypy \ + ./tests + pep8 tests + +test: lp-test + PYTHONPATH=./ ./ipsilon/tools/saml2metadata.py + sdist: python setup.py sdist diff --git a/ipsilon/install/ipsilon-server-install b/ipsilon/install/ipsilon-server-install index 844505c..1bf0072 100755 --- a/ipsilon/install/ipsilon-server-install +++ b/ipsilon/install/ipsilon-server-install @@ -41,6 +41,7 @@ DATADIR = '/var/lib/ipsilon' HTTPDCONFD = '/etc/httpd/conf.d' BINDIR = '/usr/sbin' STATICDIR = '/usr/share/ipsilon' +WSGI_SOCKET_PREFIX = None class ConfigurationError(Exception): @@ -102,6 +103,8 @@ def install(plugins, args): 'sysuser': args['system_user'], 'ipsilondir': BINDIR, 'staticdir': STATICDIR} + if WSGI_SOCKET_PREFIX: + confopts['wsgi_socket'] = 'WSGISocketPrefix %s' % WSGI_SOCKET_PREFIX files.write_from_template(ipsilon_conf, os.path.join(TEMPLATES, 'ipsilon.conf'), confopts) diff --git a/templates/install/idp.conf b/templates/install/idp.conf index 6175ffe..c49992b 100644 --- a/templates/install/idp.conf +++ b/templates/install/idp.conf @@ -1,6 +1,7 @@ Alias /${instance}/ui ${staticdir}/ui WSGIScriptAlias /${instance} ${ipsilondir}/ipsilon WSGIDaemonProcess ${instance} user=${sysuser} group=${sysuser} home=${datadir} +${wsgi_socket} WSGIProcessGroup ${instance} diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/httpd.conf b/tests/httpd.conf new file mode 100644 index 0000000..6b2a4cb --- /dev/null +++ b/tests/httpd.conf @@ -0,0 +1,107 @@ +ServerRoot "${HTTPROOT}" +Listen ${HTTPADDR}:${HTTPPORT} + +LoadModule access_compat_module modules/mod_access_compat.so +LoadModule actions_module modules/mod_actions.so +LoadModule alias_module modules/mod_alias.so +LoadModule allowmethods_module modules/mod_allowmethods.so +LoadModule auth_basic_module modules/mod_auth_basic.so +#LoadModule auth_digest_module modules/mod_auth_digest.so +LoadModule authn_anon_module modules/mod_authn_anon.so +LoadModule authn_core_module modules/mod_authn_core.so +LoadModule authn_dbd_module modules/mod_authn_dbd.so +LoadModule authn_dbm_module modules/mod_authn_dbm.so +LoadModule authn_file_module modules/mod_authn_file.so +LoadModule authn_socache_module modules/mod_authn_socache.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule authz_dbd_module modules/mod_authz_dbd.so +LoadModule authz_dbm_module modules/mod_authz_dbm.so +LoadModule authz_groupfile_module modules/mod_authz_groupfile.so +LoadModule authz_host_module modules/mod_authz_host.so +LoadModule authz_owner_module modules/mod_authz_owner.so +LoadModule authz_user_module modules/mod_authz_user.so +LoadModule autoindex_module modules/mod_autoindex.so +LoadModule cache_module modules/mod_cache.so +LoadModule cache_disk_module modules/mod_cache_disk.so +LoadModule data_module modules/mod_data.so +LoadModule dbd_module modules/mod_dbd.so +LoadModule deflate_module modules/mod_deflate.so +LoadModule dir_module modules/mod_dir.so +LoadModule dumpio_module modules/mod_dumpio.so +LoadModule echo_module modules/mod_echo.so +LoadModule env_module modules/mod_env.so +LoadModule expires_module modules/mod_expires.so +LoadModule ext_filter_module modules/mod_ext_filter.so +LoadModule filter_module modules/mod_filter.so +LoadModule headers_module modules/mod_headers.so +LoadModule include_module modules/mod_include.so +LoadModule info_module modules/mod_info.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule logio_module modules/mod_logio.so +LoadModule macro_module modules/mod_macro.so +LoadModule mime_magic_module modules/mod_mime_magic.so +LoadModule mime_module modules/mod_mime.so +LoadModule negotiation_module modules/mod_negotiation.so +LoadModule remoteip_module modules/mod_remoteip.so +LoadModule reqtimeout_module modules/mod_reqtimeout.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule setenvif_module modules/mod_setenvif.so +LoadModule slotmem_plain_module modules/mod_slotmem_plain.so +LoadModule slotmem_shm_module modules/mod_slotmem_shm.so +LoadModule socache_dbm_module modules/mod_socache_dbm.so +LoadModule socache_memcache_module modules/mod_socache_memcache.so +LoadModule socache_shmcb_module modules/mod_socache_shmcb.so +LoadModule status_module modules/mod_status.so +LoadModule substitute_module modules/mod_substitute.so +LoadModule suexec_module modules/mod_suexec.so +LoadModule unique_id_module modules/mod_unique_id.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule userdir_module modules/mod_userdir.so +LoadModule version_module modules/mod_version.so +LoadModule vhost_alias_module modules/mod_vhost_alias.so + +LoadModule mpm_prefork_module modules/mod_mpm_prefork.so +LoadModule wsgi_module modules/mod_wsgi.so +LoadModule auth_mellon_module modules/mod_auth_mellon.so + + + AllowOverride none + Require all denied + + +DocumentRoot "${HTTPROOT}/html" + + AllowOverride None + # Allow open access: + Require all granted + + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + + + DirectoryIndex index.html + + + + Require all denied + + +PidFile "${HTTPROOT}/logs/httpd.pid" + +ErrorLog "logs/error_log" +LogLevel debug + + + TypesConfig /etc/mime.types + AddType application/x-compress .Z + AddType application/x-gzip .gz .tgz + AddType text/html .shtml + AddOutputFilter INCLUDES .shtml + + +AddDefaultCharset UTF-8 + +IncludeOptional conf.d/*.conf diff --git a/tests/tests.py b/tests/tests.py new file mode 100755 index 0000000..cfd63c6 --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# +# Copyright (C) 2014 Simo Sorce +# +# see file 'COPYING' for use and warranty information +# +# 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 3 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, see . + +import argparse +import ConfigParser +from datetime import datetime +import logging +import os +import pwd +import shutil +import signal +import subprocess +import sys +from string import Template + + +logger = None + + +def parse_args(): + parser = argparse.ArgumentParser(description='Ipsilon Tests Environment') + parser.add_argument('--path', default='%s/testdir' % os.getcwd(), + help="Directory in which tests are run") + parser.add_argument('--test', default='test1', + help="The test to run") + + return vars(parser.parse_args()) + + +def openlogs(path, test): + global logger # pylint: disable=W0603 + logger = logging.getLogger() + try: + datestr = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + filename = '%s/test-%s-%s.log' % (path, test, datestr) + lh = logging.FileHandler(filename) + except IOError, e: + print >> sys.stderr, 'Unable to open %s (%s)' % (filename, str(e)) + lh = logging.StreamHandler(sys.stderr) + formatter = logging.Formatter('[%(asctime)s] %(message)s') + lh.setFormatter(formatter) + logger.addHandler(lh) + logger.setLevel(logging.DEBUG) + + +def force_remove(op, name, info): + os.chmod(name, 0700) + os.remove(name) + + +def setup_http(httpdir, addr, port): + os.mkdir(httpdir) + os.mkdir(httpdir + '/conf.d') + os.mkdir(httpdir + '/html') + os.mkdir(httpdir + '/logs') + os.symlink('/etc/httpd/modules', httpdir + '/modules') + + with open('tests/httpd.conf') as f: + t = Template(f.read()) + text = t.substitute({'HTTPROOT': httpdir, + 'HTTPADDR': addr, 'HTTPPORT': port}) + with open(httpdir + '/httpd.conf', 'w+') as f: + f.write(text) + + +def setup_test(path, test): + profile = 'tests/%s.cfg' % test + if not os.path.exists(profile): + raise ValueError('Unrecognized test name [%s]' % test) + + opts = {} + config = ConfigParser.ConfigParser() + config.read(profile) + if 'tests' not in config.sections(): + raise ValueError('Missing [tests] in profile [%s]' % test) + T = config.options('tests') + for t in T: + opts[t] = config.get('tests', t) + + base = '%s/%s' % (path, test) + if os.path.exists(base): + shutil.rmtree(base, onerror=force_remove) + os.makedirs(base) + shutil.copytree('templates', base + '/templates') + os.mkdir(base + '/etc') + os.mkdir(base + '/lib') + os.mkdir(base + '/lib/' + test) + os.mkdir(base + '/log') + + with open(profile) as f: + t = Template(f.read()) + text = t.substitute({'TESTDIR': base, 'ROOTDIR': os.getcwd(), + 'TEST_USER': pwd.getpwuid(os.getuid())[0]}) + with open(base + '/profile.cfg', 'w+') as f: + f.write(text) + + opts['basedir'] = base + return opts + + +def generate_profile(profile, name): + config = ConfigParser.ConfigParser() + config.read(profile) + + global_section = '%s_globals' % name + global_options = {} + if global_section in config.sections(): + G = config.options(global_section) + for g in G: + global_options[g] = config.get(global_section, g) + + args_section = '%s_arguments' % name + args_options = {} + if args_section in config.sections(): + A = config.options(args_section) + for a in A: + args_options[a] = config.get(args_section, a) + + newconf = ConfigParser.ConfigParser() + newconf.add_section('globals') + for k in global_options.keys(): + newconf.set('globals', k, global_options[k]) + newconf.add_section('arguments') + for k in args_options.keys(): + newconf.set('arguments', k, args_options[k]) + + filename = os.path.join(os.path.dirname(profile), '%s_profile.cfg' % name) + with open(filename, 'wb') as f: + newconf.write(f) + + return filename + + +def fixup_sp_httpd(httpdir): + location = """ + +Alias /sp ${HTTPDIR}/sp + + + Require all granted + +""" + index = """WORKS!""" + + t = Template(location) + text = t.substitute({'HTTPDIR': httpdir}) + with open(httpdir + '/conf.d/ipsilon-saml.conf', 'a') as f: + f.write(text) + + os.mkdir(httpdir + '/sp') + with open(httpdir + '/sp/index.html', 'w') as f: + f.write(index) + +if __name__ == '__main__': + + args = parse_args() + + if not os.path.exists(args['path']): + os.makedirs(args['path']) + openlogs(args['path'], args['test']) + + options = setup_test(args['path'], args['test']) + basedir = options['basedir'] + + env={'PYTHONPATH':'./'} + srvs = [] + try: + for h in options['servers'].split(','): + sname, saddr, sport = h.split(':') + basehttpdir = '%s/%s' % (basedir, sname) + setup_http(basehttpdir, saddr, sport) + + sprofile = generate_profile('%s/profile.cfg' % basedir, sname) + p = subprocess.Popen(['./ipsilon/install/ipsilon-server-install', + '--config-profile=%s' % sprofile], env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + logger.error(stderr) + logger.info(stdout) + if p.returncode: + sys.exit(p.returncode) + + os.symlink('%s/ipsilon' % os.getcwd(), + '%s/lib/%s/ipsilon' % (basedir, sname)) + + print "Starting httpd server in %s" % basehttpdir + srv = subprocess.Popen(['/usr/sbin/httpd', '-DFOREGROUND', + '-f', basehttpdir + '/httpd.conf'], + env=env, preexec_fn=os.setsid) + srvs.append(srv) + + for h in options['clients'].split(','): + sname, saddr, sport = h.split(':') + basehttpdir = '%s/%s' % (basedir, sname) + setup_http(basehttpdir, saddr, sport) + + sprofile = generate_profile('%s/profile.cfg' % basedir, sname) + p = subprocess.Popen(['./ipsilon/install/ipsilon-client-install', + '--config-profile=%s' % sprofile], env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + logger.error(stderr) + logger.info(stdout) + if p.returncode: + sys.exit(p.returncode) + + fixup_sp_httpd(basehttpdir) + + print "Starting httpd server in %s" % basehttpdir + srv = subprocess.Popen(['/usr/sbin/httpd', '-DFOREGROUND', + '-f', basehttpdir + '/httpd.conf'], + env=env, preexec_fn=os.setsid) + srvs.append(srv) + + if os.path.exists('tests/%s.py' % args['test']): + code = subprocess.call(['./tests/%s.py' % args['test'], basedir], + env=env) + if code: + sys.exit(code) + except Exception: # pylint: disable=broad-except + sys.exit(1) + finally: + for srv in srvs: + os.killpg(srv.pid, signal.SIGTERM) + + print "FINISHED" -- 2.20.1