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:
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user