refactor: simplify Oracle pool to use only ORACLE_SERVERS format

- Remove legacy pool support (DSN, env vars fallback)
- Use first registered server when server_id not specified
- Show server dropdown even with single server in ORACLE_SERVERS
- Email login only available for 2+ servers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-02-03 15:15:57 +00:00
parent 6718c956f7
commit 2e1ead69e1
2 changed files with 26 additions and 99 deletions

View File

@@ -1,12 +1,11 @@
""" """
Oracle Database Connection Pool - Multi-Server Support for ROA2WEB Oracle Database Connection Pool - Multi-Server Support for ROA2WEB
Supports both single-server (backward compatible) and multi-server configurations. Uses ORACLE_SERVERS from .env for server configuration.
Pool-uri sunt create lazy (la prima conexiune pe fiecare server) pentru optimizare. Pool-uri sunt create lazy (la prima conexiune pe fiecare server) pentru optimizare.
""" """
import asyncio import asyncio
import oracledb import oracledb
import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
import logging import logging
@@ -21,14 +20,13 @@ class OracleMultiPool:
Supports: Supports:
- Multiple Oracle servers with separate pools: {server_id: pool} - Multiple Oracle servers with separate pools: {server_id: pool}
- Lazy pool creation (created on first connection) - Lazy pool creation (created on first connection)
- Backward compatibility (default server when no server_id specified) - First registered server used when no server_id specified
- Graceful shutdown of all pools - Graceful shutdown of all pools
""" """
_instance: Optional['OracleMultiPool'] = None _instance: Optional['OracleMultiPool'] = None
_pools: Dict[str, oracledb.ConnectionPool] _pools: Dict[str, oracledb.ConnectionPool]
_pool_configs: Dict[str, Dict[str, Any]] _pool_configs: Dict[str, Dict[str, Any]]
_pool_lock: asyncio.Lock _pool_lock: asyncio.Lock
_legacy_pool: Optional[oracledb.ConnectionPool] # For backward compatibility
_initialized: bool _initialized: bool
def __new__(cls): def __new__(cls):
@@ -37,77 +35,23 @@ class OracleMultiPool:
cls._instance._pools = {} cls._instance._pools = {}
cls._instance._pool_configs = {} cls._instance._pool_configs = {}
cls._instance._pool_lock = asyncio.Lock() cls._instance._pool_lock = asyncio.Lock()
cls._instance._legacy_pool = None
cls._instance._initialized = False cls._instance._initialized = False
return cls._instance return cls._instance
async def initialize(self, **config): async def initialize(self):
""" """
Initialize pool manager. Initialize pool manager.
For backward compatibility, this can: Call this after registering servers with register_server().
1. Create a legacy single pool (if called with individual params) Pools are created lazily on first connection.
2. Just mark as initialized (if using lazy multi-pool loading)
""" """
if self._initialized: if self._initialized:
logger.debug("Pool manager already initialized") logger.debug("Pool manager already initialized")
return return
# Check if we have DSN or individual parameters (legacy mode)
dsn = config.get('dsn', os.getenv('ORACLE_DSN'))
user = config.get('user', os.getenv('ORACLE_USER'))
if dsn or user:
# Legacy single-pool mode - create pool immediately
await self._create_legacy_pool(config)
self._initialized = True self._initialized = True
logger.info("Oracle pool manager initialized") logger.info("Oracle pool manager initialized")
async def _create_legacy_pool(self, config: Dict[str, Any]) -> None:
"""Create legacy single pool for backward compatibility."""
dsn = config.get('dsn', os.getenv('ORACLE_DSN'))
if dsn:
# Use DSN connection
self._legacy_pool = oracledb.create_pool(
user=config.get('user', os.getenv('ORACLE_USER')),
password=config.get('password', os.getenv('ORACLE_PASSWORD')),
dsn=dsn,
min=config.get('min_connections', 2),
max=config.get('max_connections', 10),
increment=config.get('increment', 1),
getmode=oracledb.POOL_GETMODE_WAIT
)
else:
# Use individual parameters (host, port, service_name or sid)
service_name = config.get('service_name', os.getenv('ORACLE_SERVICE_NAME'))
sid = config.get('sid', os.getenv('ORACLE_SID'))
pool_params = {
'user': config.get('user', os.getenv('ORACLE_USER')),
'password': config.get('password', os.getenv('ORACLE_PASSWORD')),
'host': config.get('host', os.getenv('ORACLE_HOST', 'localhost')),
'port': config.get('port', int(os.getenv('ORACLE_PORT', '1526'))),
'min': config.get('min_connections', 2),
'max': config.get('max_connections', 10),
'increment': config.get('increment', 1),
'getmode': oracledb.POOL_GETMODE_WAIT
}
if service_name:
pool_params['service_name'] = service_name
logger.info(f"Using SERVICE_NAME: {service_name}")
elif sid:
pool_params['sid'] = sid
logger.info(f"Using SID: {sid}")
else:
pool_params['service_name'] = 'ROA'
logger.info("Using default SERVICE_NAME: ROA")
self._legacy_pool = oracledb.create_pool(**pool_params)
logger.info(f"Legacy Oracle pool created with {self._legacy_pool.opened} connections")
def register_server( def register_server(
self, self,
server_id: str, server_id: str,
@@ -185,20 +129,26 @@ class OracleMultiPool:
logger.info(f"Pool created for server '{server_id}' with {pool.opened} connections") logger.info(f"Pool created for server '{server_id}' with {pool.opened} connections")
return pool return pool
def _get_first_server_id(self) -> str:
"""Get the first registered server ID."""
if not self._pool_configs:
raise RuntimeError("No servers registered. Call register_server() first.")
return next(iter(self._pool_configs))
@asynccontextmanager @asynccontextmanager
async def get_connection(self, server_id: Optional[str] = None): async def get_connection(self, server_id: Optional[str] = None):
""" """
Context manager pentru obținerea unei conexiuni din pool. Context manager pentru obținerea unei conexiuni din pool.
Args: Args:
server_id: ID-ul serverului. Dacă None, folosește legacy pool sau default. server_id: ID-ul serverului. Dacă None, folosește primul server înregistrat.
Usage: Usage:
# Multi-server mode # Explicit server
async with oracle_pool.get_connection('romfast') as conn: async with oracle_pool.get_connection('romfast') as conn:
... ...
# Backward compatible (legacy single pool) # First registered server (when only one server configured)
async with oracle_pool.get_connection() as conn: async with oracle_pool.get_connection() as conn:
... ...
""" """
@@ -207,21 +157,11 @@ class OracleMultiPool:
try: try:
if server_id is None: if server_id is None:
# Backward compatibility: use legacy pool # Use first registered server
if self._legacy_pool is None: server_id = self._get_first_server_id()
# If no legacy pool, try to use 'default' server logger.debug(f"No server_id specified, using first registered: '{server_id}'")
if 'default' in self._pool_configs:
pool = await self._get_or_create_pool('default')
else:
raise RuntimeError(
"No pool available. Either initialize() with config "
"or register_server() with server_id='default'."
)
else:
pool = self._legacy_pool
else:
pool = await self._get_or_create_pool(server_id)
pool = await self._get_or_create_pool(server_id)
connection = pool.acquire() connection = pool.acquire()
logger.debug(f"Connection acquired from pool (server_id={server_id})") logger.debug(f"Connection acquired from pool (server_id={server_id})")
yield connection yield connection
@@ -238,7 +178,7 @@ class OracleMultiPool:
Args: Args:
query: SQL query string query: SQL query string
parameters: Query parameters (dict or tuple) parameters: Query parameters (dict or tuple)
server_id: Server ID for multi-pool mode (optional) server_id: Server ID (optional, uses first server if not specified)
""" """
async with self.get_connection(server_id) as connection: async with self.get_connection(server_id) as connection:
logger.debug(f"Executing query on server '{server_id}': {query[:100]}...") logger.debug(f"Executing query on server '{server_id}': {query[:100]}...")
@@ -272,11 +212,6 @@ class OracleMultiPool:
logger.info(f"Closed pool for server '{server_id}'") logger.info(f"Closed pool for server '{server_id}'")
else: else:
# Close all pools (graceful shutdown) # Close all pools (graceful shutdown)
if self._legacy_pool is not None:
self._legacy_pool.close()
self._legacy_pool = None
logger.info("Closed legacy pool")
for srv_id, pool in list(self._pools.items()): for srv_id, pool in list(self._pools.items()):
pool.close() pool.close()
logger.info(f"Closed pool for server '{srv_id}'") logger.info(f"Closed pool for server '{srv_id}'")
@@ -307,15 +242,6 @@ class OracleMultiPool:
'max': pool.max, 'max': pool.max,
} }
else: else:
# All pools including legacy
if self._legacy_pool:
stats['legacy'] = {
'opened': self._legacy_pool.opened,
'busy': self._legacy_pool.busy,
'min': self._legacy_pool.min,
'max': self._legacy_pool.max,
}
for srv_id, pool in self._pools.items(): for srv_id, pool in self._pools.items():
stats[srv_id] = { stats[srv_id] = {
'opened': pool.opened, 'opened': pool.opened,
@@ -343,8 +269,8 @@ class OracleMultiPool:
return list(self._pools.keys()) return list(self._pools.keys())
# Backward compatibility: keep old class name as alias # Backward compatibility alias
OraclePool = OracleMultiPool OraclePool = OracleMultiPool
# Instance globală pentru folosire în toate aplicațiile # Global instance
oracle_pool = OracleMultiPool() oracle_pool = OracleMultiPool()

View File

@@ -59,14 +59,15 @@ def create_system_router() -> APIRouter:
servers = settings.get_oracle_servers() servers = settings.get_oracle_servers()
# Multi-server mode: 2+ servers configured via ORACLE_SERVERS # Multi-server mode: ANY servers configured via ORACLE_SERVERS
if servers and len(servers) > 1: # Shows server dropdown even with 1 server (explicit server selection)
if servers and len(servers) >= 1:
return AuthModeResponse( return AuthModeResponse(
mode="multi-server", mode="multi-server",
supports_email_login=True supports_email_login=len(servers) > 1 # Email lookup only for 2+ servers
) )
# Single-server mode: legacy config or single ORACLE_SERVERS entry # Single-server mode: legacy config (no ORACLE_SERVERS, uses env vars)
return AuthModeResponse( return AuthModeResponse(
mode="single-server", mode="single-server",
supports_email_login=False supports_email_login=False