emmett-framework / granian

A Rust HTTP server for Python applications
BSD 3-Clause "New" or "Revised" License
2.85k stars 84 forks source link

Can't load wsgi file without .py ext #379

Closed vooon closed 3 months ago

vooon commented 3 months ago

Out of curiosity I've tried to use Granian to run OpenStack Keystone.

With default pbr generated wsgi file that's not possible:

$ granian --version
granian 1.5.2
$ /usr/bin/granian --host 192.168.50.84 --port 15000 --interface wsgi --workers 2 --threads 4 --log-level debug /usr/bin/keystone-wsgi-public:application
[INFO] Websockets are not supported on WSGI
[INFO] Starting granian (main PID: 110004)
[INFO] Listening at: http://192.168.50.84:15000
[INFO] Spawning worker-1 with pid: 110008
[INFO] Spawning worker-2 with pid: 110010
Process granian-worker:
Traceback (most recent call last):
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 40, in load_module
    __import__(module_name)
ModuleNotFoundError: No module named 'keystone-wsgi-public'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib64/python3.9/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/usr/lib64/python3.9/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib64/python3.9/site-packages/granian/server.py", line 374, in _spawn_wsgi_worker
    callback = callback_loader()
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 58, in load_target
    module = load_module(path)
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 47, in load_module
    raise RuntimeError(f"Could not import '{module_name}'.")
RuntimeError: Could not import 'keystone-wsgi-public'.
[ERROR] Unexpected exit from worker-1
[INFO] Shutting down granian
Process granian-worker:
Traceback (most recent call last):
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 40, in load_module
    __import__(module_name)
ModuleNotFoundError: No module named 'keystone-wsgi-public'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib64/python3.9/multiprocessing/process.py", line 315, in _bootstrap
    self.run()
  File "/usr/lib64/python3.9/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib64/python3.9/site-packages/granian/server.py", line 374, in _spawn_wsgi_worker
    callback = callback_loader()
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 58, in load_target
    module = load_module(path)
  File "/usr/lib64/python3.9/site-packages/granian/_internal.py", line 47, in load_module
    raise RuntimeError(f"Could not import '{module_name}'.")
RuntimeError: Could not import 'keystone-wsgi-public'.

I copied wsgi to have .py ext - module loads, but then problem came from argv's not cleaned from Granian options.

# /usr/bin/granian --host 192.168.50.84 --port 15000 --interface wsgi --workers 2 --threads 4 --log-level debug /etc/granian/keystone_wsgi_public.py:application
[INFO] Websockets are not supported on WSGI
[INFO] Starting granian (main PID: 107680)
[INFO] Listening at: http://192.168.50.84:15000
[INFO] Spawning worker-1 with pid: 107681
[INFO] Spawning worker-2 with pid: 107683
usage: granian [-h] [--config-dir DIR] [--config-file PATH] [--debug] [--log-config-append PATH] [--log-date-format DATE_FORMAT] [--log-dir LOG_DIR] [--log-file PATH] [--nodebug] [--nostandard-threads] [--nouse-journal] [--nouse-json]
               [--nouse-syslog] [--nowatch-log-file] [--pydev-debug-host PYDEV_DEBUG_HOST] [--pydev-debug-port PYDEV_DEBUG_PORT] [--standard-threads] [--syslog-log-facility SYSLOG_LOG_FACILITY] [--use-journal] [--use-json] [--use-syslog]
               [--watch-log-file]
granian: error: unrecognized arguments: --host 192.168.50.84 --port 15000 --interface wsgi --workers 2 --threads 4 --log-level debug /etc/granian/keystone_wsgi_public.py:application
usage: granian [-h] [--config-dir DIR] [--config-file PATH] [--debug] [--log-config-append PATH] [--log-date-format DATE_FORMAT] [--log-dir LOG_DIR] [--log-file PATH] [--nodebug] [--nostandard-threads] [--nouse-journal] [--nouse-json]
               [--nouse-syslog] [--nowatch-log-file] [--pydev-debug-host PYDEV_DEBUG_HOST] [--pydev-debug-port PYDEV_DEBUG_PORT] [--standard-threads] [--syslog-log-facility SYSLOG_LOG_FACILITY] [--use-journal] [--use-json] [--use-syslog]
               [--watch-log-file]
granian: error: unrecognized arguments: --host 192.168.50.84 --port 15000 --interface wsgi --workers 2 --threads 4 --log-level debug /etc/granian/keystone_wsgi_public.py:application
gi0baro commented 3 months ago

Seems a keystone related issue, as the target you set to Granian is not actually the WSGI application, but an executable script.

I'm pretty confident gunicorn or hypercorn will exhibit the same behaviour. Googling a bit it seems there's a way to serve the application direcly, see: https://freehackers.org/thomas/2016/04/17/using-keystone-mitaka-with-gunicorn-and-nginx/

Closing this, as there's nothing Granian can do in this case.

vooon commented 3 months ago

@gi0baro i managed to run it with custom wrapper (made of the original file):

import sys
import threading
from keystone.server.wsgi import initialize_public_application

application = None
app_lock = threading.Lock()

with app_lock:
    # remove granian options
    sys.argv = [sys.argv[0]]

    if application is None:
        application = initialize_public_application()

Where i can find doc on logging config?

gi0baro commented 3 months ago

@gi0baro i managed to run it with custom wrapper (made of the original file):

it should work also without the threading lock, as application import is done on each worker (but those are separated processes, not threads) and from a single thread.

Where i can find doc on logging config?

We're still managing to write documentation, but the logging config in Granian relies on standard logging dict config. You can look at the default configuration directly in the code: https://github.com/emmett-framework/granian/blob/master/granian/log.py#L28. Access logs atoms are in the README instead.