0002-add-uwsgidecorators-module-57019.patch
hobo/multitenant/uwsgidecorators.py | ||
---|---|---|
1 |
# Copyright (C) 2021 Entr'ouvert |
|
2 | ||
3 |
import contextlib |
|
4 |
import logging |
|
5 |
import pickle |
|
6 |
import sys |
|
7 | ||
8 |
try: |
|
9 |
import uwsgi |
|
10 |
except ImportError: |
|
11 |
uwsgi = None |
|
12 | ||
13 |
logger = logging.getLogger(__name__) |
|
14 | ||
15 |
spooler_registry = {} |
|
16 | ||
17 | ||
18 |
@contextlib.contextmanager |
|
19 |
def close_db(): |
|
20 |
if 'django' in sys.modules: |
|
21 |
from django.db import close_old_connections |
|
22 | ||
23 |
close_old_connections() |
|
24 |
try: |
|
25 |
yield None |
|
26 |
finally: |
|
27 |
close_old_connections() |
|
28 |
else: |
|
29 |
yield |
|
30 | ||
31 | ||
32 |
@contextlib.contextmanager |
|
33 |
def tenant_context(domain): |
|
34 |
if domain: |
|
35 |
from tenant_schemas.utils import tenant_context |
|
36 | ||
37 |
from hobo.multitenant.middleware import TenantMiddleware |
|
38 | ||
39 |
tenant = TenantMiddleware.get_tenant_by_hostname(domain) |
|
40 |
with tenant_context(tenant): |
|
41 |
yield |
|
42 |
else: |
|
43 |
yield |
|
44 | ||
45 | ||
46 |
def get_tenant(): |
|
47 |
if 'django.db' not in sys.modules: |
|
48 |
return '' |
|
49 |
from django.db import connection |
|
50 | ||
51 |
tenant_model = getattr(connection, 'tenant', None) |
|
52 |
return getattr(tenant_model, 'domain_url', '') |
|
53 | ||
54 | ||
55 |
def spool(func): |
|
56 |
if uwsgi: |
|
57 |
name = '%s.%s' % (func.__module__, func.__name__) |
|
58 |
spooler_registry[name] = func |
|
59 | ||
60 |
def spool_function(*args, **kwargs): |
|
61 |
uwsgi.spool( |
|
62 |
name=name.encode(), |
|
63 |
tenant=get_tenant().encode(), |
|
64 |
body=pickle.dumps({'args': args, 'kwargs': kwargs}), |
|
65 |
) |
|
66 |
logger.debug('spooler: spooled function %s', name) |
|
67 | ||
68 |
func.spool = spool_function |
|
69 |
return func |
|
70 | ||
71 | ||
72 |
if uwsgi: |
|
73 | ||
74 |
def spooler_function(env): |
|
75 |
try: |
|
76 |
try: |
|
77 |
name = env.get('name').decode() |
|
78 |
tenant = env.get('tenant', b'').decode() |
|
79 |
body = env.get('body') |
|
80 |
except Exception: |
|
81 |
logger.error('spooler: no name or body found: env.keys()=%s', env.keys()) |
|
82 |
return uwsgi.SPOOL_OK |
|
83 |
try: |
|
84 |
params = pickle.loads(body) |
|
85 |
args = params['args'] |
|
86 |
kwargs = params['kwargs'] |
|
87 |
except Exception: |
|
88 |
logger.exception('spooler: depickling of body failed') |
|
89 |
return uwsgi.SPOOL_OK |
|
90 |
try: |
|
91 |
function = spooler_registry[name] |
|
92 |
except KeyError: |
|
93 |
logger.error('spooler: no function named "%s"', name) |
|
94 |
# prevent connections to leak between jobs |
|
95 |
# maintain current tenant when spool is launched |
|
96 |
with close_db(), tenant_context(tenant): |
|
97 |
function(*args, **kwargs) |
|
98 |
except Exception: |
|
99 |
logger.exception('spooler: function "%s" raised' % name) |
|
100 |
return uwsgi.SPOOL_OK |
|
101 | ||
102 |
uwsgi.spooler = spooler_function |
tests_multitenant/test_uwsgidecorators.py | ||
---|---|---|
1 |
import importlib |
|
2 |
import pickle |
|
3 | ||
4 |
import mock |
|
5 |
import pytest |
|
6 | ||
7 |
import hobo.multitenant.uwsgidecorators |
|
8 | ||
9 | ||
10 |
@pytest.fixture |
|
11 |
def uwsgi(): |
|
12 |
import sys |
|
13 | ||
14 |
uwsgi = mock.Mock() |
|
15 |
uwsgi.SPOOL_OK = -2 |
|
16 |
sys.modules['uwsgi'] = uwsgi |
|
17 |
importlib.reload(hobo.multitenant.uwsgidecorators) |
|
18 |
yield uwsgi |
|
19 |
del sys.modules['uwsgi'] |
|
20 |
importlib.reload(hobo.multitenant.uwsgidecorators) |
|
21 | ||
22 | ||
23 |
def test_basic(): |
|
24 |
@hobo.multitenant.uwsgidecorators.spool |
|
25 |
def function(a, b): |
|
26 |
pass |
|
27 | ||
28 |
function(1, 2) |
|
29 |
with pytest.raises(AttributeError): |
|
30 |
function.spool(1, 2) |
|
31 | ||
32 | ||
33 |
def test_mocked_uwsgi(uwsgi): |
|
34 |
@hobo.multitenant.uwsgidecorators.spool |
|
35 |
def function(a, b): |
|
36 |
pass |
|
37 | ||
38 |
function(1, 2) |
|
39 |
function.spool(1, 2) |
|
40 |
assert set(uwsgi.spool.call_args[1].keys()) == {'body', 'tenant', 'name'} |
|
41 |
assert pickle.loads(uwsgi.spool.call_args[1]['body']) == {'args': (1, 2), 'kwargs': {}} |
|
42 |
assert uwsgi.spool.call_args[1]['name'] == b'test_uwsgidecorators.function' |
|
43 |
assert uwsgi.spool.call_args[1]['tenant'] == b'' |
|
44 | ||
45 | ||
46 |
def test_mocked_uwsgi_tenant(uwsgi, tenant): |
|
47 |
from tenant_schemas.utils import tenant_context |
|
48 | ||
49 |
@hobo.multitenant.uwsgidecorators.spool |
|
50 |
def function(a, b): |
|
51 |
pass |
|
52 | ||
53 |
with tenant_context(tenant): |
|
54 |
function.spool(1, 2) |
|
55 | ||
56 |
assert set(uwsgi.spool.call_args[1].keys()) == {'body', 'tenant', 'name'} |
|
57 |
assert pickle.loads(uwsgi.spool.call_args[1]['body']) == {'args': (1, 2), 'kwargs': {}} |
|
58 |
assert uwsgi.spool.call_args[1]['name'] == b'test_uwsgidecorators.function' |
|
59 |
assert uwsgi.spool.call_args[1]['tenant'] == b'tenant.example.net' |
|
0 |
- |