81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
Tenant Request Routing Pipeline (ដំណើរការ Request)
|
||
|
|
-----------------------------------------------------
|
||
|
|
1. User Request -> company1.domain-name.com
|
||
|
|
2. DNS Resolution -> Load Balancer IP
|
||
|
|
3. Load Balancer -> Route to available Worker
|
||
|
|
4. Worker -> Read database name from subdomain
|
||
|
|
5. Database Router -> Connect to correct tenant database
|
||
|
|
6. Process Request -> Return response
|
||
|
|
|
||
|
|
Steps 1-3 happen OUTSIDE Odoo, at the infrastructure layer:
|
||
|
|
- DNS: wildcard A/CNAME record *.domain-name.com -> Load Balancer IP
|
||
|
|
- Load Balancer (nginx/HAProxy/Traefik) terminates TLS and forwards
|
||
|
|
to one of N Odoo worker processes/pods, e.g.:
|
||
|
|
|
||
|
|
nginx.conf snippet:
|
||
|
|
server {
|
||
|
|
listen 443 ssl;
|
||
|
|
server_name *.domain-name.com;
|
||
|
|
location / {
|
||
|
|
proxy_pass http://odoo_workers;
|
||
|
|
proxy_set_header Host $host;
|
||
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
upstream odoo_workers {
|
||
|
|
server worker-1:8069;
|
||
|
|
server worker-2:8069;
|
||
|
|
server worker-3:8069;
|
||
|
|
}
|
||
|
|
|
||
|
|
Steps 4-6 are implemented in Odoo itself via this controller, which reads
|
||
|
|
the subdomain from the Host header and switches `request.session.db`
|
||
|
|
before any other controller/model code executes. Odoo natively supports
|
||
|
|
this via the `dbfilter` config option (odoo.conf):
|
||
|
|
|
||
|
|
dbfilter = ^%h$
|
||
|
|
|
||
|
|
`%h` is replaced by the Host header at request time, so Odoo automatically
|
||
|
|
maps "company1.domain-name.com" -> database "company1" PROVIDED the
|
||
|
|
database name matches the first subdomain label. Since our generated
|
||
|
|
db_name (see saas.database._generate_unique_db_name) IS the first label
|
||
|
|
of the subdomain, `dbfilter = ^%h$` alone satisfies steps 4-5 for the
|
||
|
|
standard Odoo multi-database filter mechanism.
|
||
|
|
|
||
|
|
The explicit controller below is only needed if you want CUSTOM routing
|
||
|
|
logic beyond dbfilter (e.g. custom error pages for suspended/expired
|
||
|
|
tenants, or a non-Odoo-native subdomain naming scheme).
|
||
|
|
"""
|
||
|
|
from odoo import http
|
||
|
|
from odoo.http import request
|
||
|
|
|
||
|
|
|
||
|
|
class TenantRouterController(http.Controller):
|
||
|
|
|
||
|
|
@http.route('/saas/tenant-status', type='json', auth='public')
|
||
|
|
def tenant_status(self, **kw):
|
||
|
|
"""Optional health endpoint the Load Balancer / monitoring system
|
||
|
|
can call to verify a tenant database is reachable and active
|
||
|
|
before routing traffic to it (step 3-4 sanity check).
|
||
|
|
"""
|
||
|
|
host = request.httprequest.host.split(':')[0]
|
||
|
|
subdomain_label = host.split('.')[0]
|
||
|
|
|
||
|
|
database = request.env['saas.database'].sudo().search(
|
||
|
|
[('db_name', '=', subdomain_label)], limit=1
|
||
|
|
)
|
||
|
|
if not database:
|
||
|
|
return {'status': 'not_found'}
|
||
|
|
|
||
|
|
subscription = database.trial_request_id.subscription_id
|
||
|
|
if subscription and subscription.expiry_date and subscription.expiry_date < http.fields.Datetime.now():
|
||
|
|
return {'status': 'expired', 'db_name': database.db_name}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'status': 'active' if database.state == 'ready' else database.state,
|
||
|
|
'db_name': database.db_name,
|
||
|
|
'worker_node': database.worker_node,
|
||
|
|
}
|