@@ -8,13 +8,16 @@ import subprocess
88import sys
99import tempfile
1010from collections import namedtuple
11+ from concurrent .futures import ThreadPoolExecutor
1112from itertools import chain
1213from pathlib import Path
1314from socket import getfqdn
1415
1516import jinja2
17+ from ocflib .account .search import SORRIED_SHELL
1618from ocflib .account .search import user_is_sorried
1719from ocflib .account .utils import web_dir
20+ from ocflib .infra .ldap import ldap_ocf
1821from ocflib .misc .mail import email_for_user
1922from ocflib .vhost .application import get_app_vhosts
2023from ocflib .vhost .web import get_vhosts
@@ -99,7 +102,7 @@ class VirtualHost(namedtuple('VirtualHost', (
99102 """
100103 @property
101104 def contact_email (self ):
102- return email_for_user (self .user )
105+ return email_for_user (self .user , check_exists = False )
103106
104107 @property
105108 def is_redirect (self ):
@@ -130,6 +133,8 @@ class VirtualHost(namedtuple('VirtualHost', (
130133
131134 @property
132135 def disabled (self ):
136+ if _sorried_users is not None :
137+ return self .user in _sorried_users
133138 return user_is_sorried (self .user )
134139
135140 @property
@@ -175,6 +180,25 @@ def report(*args, **kwargs):
175180 kwargs ['file' ].flush ()
176181
177182
183+ def _prefetch_vhost_lookups ():
184+ """Pre-fetch LDAP data used during template rendering.
185+
186+ VirtualHost.disabled makes a per-vhost LDAP query to check if the user is
187+ sorried. This fetches all sorried users in a single bulk query instead.
188+ """
189+ global _sorried_users
190+ with ldap_ocf () as c :
191+ c .search (
192+ 'ou=People,dc=OCF,dc=Berkeley,dc=EDU' ,
193+ f'(loginShell={ SORRIED_SHELL } )' ,
194+ attributes = ['uid' ],
195+ )
196+ _sorried_users = {entry ['uid' ].value for entry in c .entries }
197+
198+
199+ _sorried_users = None
200+
201+
178202def build_config (src_vhosts , template , dev_config = False ):
179203 vhosts = list ()
180204
@@ -405,17 +429,24 @@ def main():
405429 changed |= process_app_vhosts ()
406430
407431 if args .target == 'web' :
432+ # Fetch app vhosts, web vhosts, and sorried users in parallel
433+ # to avoid sequential LDAP round-trips (~5s total vs 30s+)
434+ with ThreadPoolExecutor (max_workers = 3 ) as pool :
435+ app_future = pool .submit (get_app_vhosts )
436+ web_future = pool .submit (get_vhosts )
437+ pool .submit (_prefetch_vhost_lookups )
438+
408439 # Build app vhosts so that they can get proxied to apphost.o.b.e
409440 # Placed before regular vhosts so they take priority in domain matching
410441 # (sometimes hosts have entries in both vhost.conf and vhost-app.conf)
411442 # Filter out dev app vhosts so they don't clobber existing vhost entries
412443 prod_app_vhosts = {
413444 domain : conf
414- for domain , conf in get_app_vhosts ().items ()
445+ for domain , conf in app_future . result ().items ()
415446 if 'dev' not in conf ['flags' ]
416447 }
417448
418- web_vhosts = get_vhosts ()
449+ web_vhosts = web_future . result ()
419450
420451 # Exclude web vhosts that overlap with app vhosts (app vhosts
421452 # take priority and are proxied directly to apphost by nginx)
0 commit comments