Skip to content

Commit 0ced25d

Browse files
committed
Basic coverage of migwsgi.
1 parent ca47e01 commit 0ced25d

File tree

2 files changed

+158
-5
lines changed

2 files changed

+158
-5
lines changed

mig/wsgi-bin/migwsgi.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,13 @@ def application(environ, start_response):
193193
*start_response* is a helper function used to deliver the client response.
194194
"""
195195

196+
def _set_os_environ(value):
197+
os.environ = value
198+
199+
return _application(environ, start_response, _format_output=format_output, _set_environ=_set_os_environ, _wrap_wsgi_errors=wrap_wsgi_errors)
200+
201+
def _application(environ, start_response, _format_output, _set_environ, _wrap_wsgi_errors=True, _config_file=None, _skip_log=False):
202+
196203
# NOTE: pass app environ including apache and query args on to sub handlers
197204
# through the usual 'os.environ' channel expected in functionality
198205
# handlers. Special care is needed to avoid various sub-interpreter
@@ -235,18 +242,18 @@ def application(environ, start_response):
235242
os_env_value))
236243

237244
# Assign updated environ to LOCAL os.environ for the rest of this session
238-
os.environ = environ
245+
_set_environ(environ)
239246

240247
# NOTE: redirect stdout to stderr in python 2 only. It breaks logger in 3
241248
# and stdout redirection apparently is already handled there.
242249
if sys.version_info[0] < 3:
243250
sys.stdout = sys.stderr
244251

245-
configuration = get_configuration_object()
252+
configuration = get_configuration_object(_config_file, _skip_log)
246253
_logger = configuration.logger
247254

248255
# NOTE: replace default wsgi errors to apache error log with our own logs
249-
wrap_wsgi_errors(environ, configuration)
256+
_wrap_wsgi_errors(environ, configuration)
250257

251258
for line in env_sync_status:
252259
_logger.debug(line)
@@ -363,7 +370,7 @@ def application(environ, start_response):
363370
output_objs.append(wsgi_entry)
364371

365372
_logger.debug("call format %r output to %s" % (backend, output_format))
366-
output = format_output(configuration, backend, ret_code, ret_msg,
373+
output = _format_output(configuration, backend, ret_code, ret_msg,
367374
output_objs, output_format)
368375
# _logger.debug("formatted %s output to %s" % (backend, output_format))
369376
# _logger.debug("output:\n%s" % [output])
@@ -396,7 +403,14 @@ def application(environ, start_response):
396403
# NOTE: send response to client but don't crash e.g. on closed connection
397404
try:
398405
start_response(status, response_headers)
406+
except IOError as ioe:
407+
_logger.warning("WSGI %s for %s could not deliver output: %s" %
408+
(backend, client_id, ioe))
409+
except Exception as exc:
410+
_logger.error("WSGI %s for %s crashed during response: %s" %
411+
(backend, client_id, exc))
399412

413+
try:
400414
# NOTE: we consistently hit download error for archive files reaching ~2GB
401415
# with showfreezefile.py on wsgi but the same on cgi does NOT suffer
402416
# the problem for the exact same files. It seems wsgi has a limited
@@ -410,7 +424,7 @@ def application(environ, start_response):
410424
_logger.info("WSGI %s yielding %d output parts (%db)" %
411425
(backend, chunk_parts, content_length))
412426
# _logger.debug("send chunked %r response to client" % backend)
413-
for i in xrange(chunk_parts):
427+
for i in list(range(chunk_parts)):
414428
# _logger.debug("WSGI %s yielding part %d / %d output parts" %
415429
# (backend, i+1, chunk_parts))
416430
# end index may be after end of content - but no problem

tests/test_mig_wsgi-bin_migwsgi.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
6+
#
7+
# This file is part of MiG.
8+
#
9+
# MiG is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation; either version 2 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# MiG is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program; if not, write to the Free Software
21+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22+
# USA.
23+
#
24+
# --- END_HEADER ---
25+
#
26+
27+
"""Unit tests for the MiG WSGI glue"""
28+
29+
from configparser import ConfigParser
30+
import importlib
31+
import os
32+
import stat
33+
import sys
34+
35+
from tests.support import MIG_BASE, MigTestCase, testmain
36+
37+
38+
def create_output_returner(arranged=None):
39+
def test_format_output(*args):
40+
return arranged
41+
return test_format_output
42+
43+
def create_wsgi_environ(config_file, env_http_host=None, env_path_info=None):
44+
environ = {}
45+
environ['MIG_CONF'] = config_file
46+
environ['HTTP_HOST'] = env_http_host
47+
environ['PATH_INFO'] = env_path_info
48+
environ['SCRIPT_URI'] = ''.join(('http://', environ['HTTP_HOST'], environ['PATH_INFO']))
49+
return environ
50+
51+
52+
def noop(*args):
53+
pass
54+
55+
56+
from tests.support import PY2, is_path_within
57+
from mig.shared.base import client_id_dir, client_dir_id, get_short_id, \
58+
invisible_path, allow_script, brief_list
59+
60+
61+
_LOCAL_MIG_BASE = '/usr/src/app' if PY2 else MIG_BASE # account for execution in container
62+
_PYTHON_MAJOR = '2' if PY2 else '3'
63+
_TEST_CONF_DIR = os.path.join(MIG_BASE, "envhelp/output/testconfs-py%s" % (_PYTHON_MAJOR,))
64+
_TEST_CONF_FILE = os.path.join(_TEST_CONF_DIR, "MiGserver.conf")
65+
_TEST_CONF_SYMLINK = os.path.join(MIG_BASE, "envhelp/output/testconfs")
66+
67+
68+
def _assert_local_config():
69+
try:
70+
link_stat = os.lstat(_TEST_CONF_SYMLINK)
71+
assert stat.S_ISLNK(link_stat.st_mode)
72+
configdir_stat = os.stat(_TEST_CONF_DIR)
73+
assert stat.S_ISDIR(configdir_stat.st_mode)
74+
config = ConfigParser()
75+
config.read([_TEST_CONF_FILE])
76+
return config
77+
except Exception as exc:
78+
raise AssertionError('local configuration invalid or missing: %s' % (str(exc),))
79+
80+
81+
def _assert_local_config_global_values(config):
82+
config_global_values = dict(config.items('GLOBAL'))
83+
84+
for path in ('mig_path', 'certs_path', 'state_path'):
85+
path_value = config_global_values.get(path)
86+
if not is_path_within(path_value, start=_LOCAL_MIG_BASE):
87+
raise AssertionError('local config contains bad path: %s=%s' % (path, path_value))
88+
89+
return config_global_values
90+
91+
92+
_WSGI_BIN = os.path.join(MIG_BASE, 'mig/wsgi-bin')
93+
94+
def _import_migwsgi():
95+
sys.path.append(_WSGI_BIN)
96+
migwsgi = importlib.import_module('migwsgi')
97+
sys.path.pop(-1)
98+
return migwsgi
99+
100+
101+
migwsgi = _import_migwsgi()
102+
103+
104+
class MigSharedConfiguration(MigTestCase):
105+
def test_xxx(self):
106+
config = _assert_local_config()
107+
config_global_values = _assert_local_config_global_values(config)
108+
109+
def fake_start_response(status, headers, exc=None):
110+
fake_start_response.calls.append((status, headers, exc))
111+
fake_start_response.calls = []
112+
113+
def fake_set_environ(value):
114+
fake_set_environ.value = value
115+
fake_set_environ.value = None
116+
117+
wsgi_environ = create_wsgi_environ(
118+
_TEST_CONF_FILE,
119+
env_http_host='localhost',
120+
env_path_info='/'
121+
)
122+
123+
test_output_returner = create_output_returner(b'')
124+
125+
yielder = migwsgi._application(wsgi_environ, fake_start_response,
126+
_format_output=test_output_returner,
127+
_set_environ=fake_set_environ,
128+
_wrap_wsgi_errors=noop,
129+
_config_file=_TEST_CONF_FILE,
130+
_skip_log=True,
131+
)
132+
chunks = list(yielder)
133+
134+
self.assertGreater(len(chunks), 0)
135+
136+
137+
138+
if __name__ == '__main__':
139+
testmain()

0 commit comments

Comments
 (0)