demo seed change 4

This commit is contained in:
Urtzi Alfaro
2025-12-14 19:05:37 +01:00
parent 4ae5356ad1
commit 82f9622411
16 changed files with 532 additions and 55 deletions

View File

@@ -6,6 +6,8 @@ Handles routing, authentication, rate limiting, and cross-cutting concerns
import asyncio
import json
import structlog
import resource
import os
from fastapi import FastAPI, Request, HTTPException, Depends, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, StreamingResponse
@@ -30,6 +32,38 @@ from shared.monitoring.metrics import MetricsCollector
setup_logging("gateway", settings.LOG_LEVEL)
logger = structlog.get_logger()
# Check file descriptor limits and warn if too low
try:
soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
if soft_limit < 1024:
logger.warning(f"Low file descriptor limit detected: {soft_limit}. Gateway may experience 'too many open files' errors.")
logger.warning(f"Recommended: Increase limit with 'ulimit -n 4096' or higher for production.")
if soft_limit < 256:
logger.error(f"Critical: File descriptor limit ({soft_limit}) is too low for gateway operation!")
else:
logger.info(f"File descriptor limit: {soft_limit} (sufficient)")
except Exception as e:
logger.debug(f"Could not check file descriptor limits: {e}")
# Check and log current working directory and permissions
try:
cwd = os.getcwd()
logger.info(f"Current working directory: {cwd}")
# Check if we can write to common log locations
test_locations = ["/var/log", "./logs", "."]
for location in test_locations:
try:
test_file = os.path.join(location, ".gateway_permission_test")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
logger.info(f"Write permission confirmed for: {location}")
except Exception as e:
logger.warning(f"Cannot write to {location}: {e}")
except Exception as e:
logger.debug(f"Could not check directory permissions: {e}")
# Create FastAPI app
app = FastAPI(
title="Bakery Forecasting API Gateway",
@@ -390,7 +424,18 @@ async def events_stream(
"""Generate server-sent events from Redis pub/sub with multi-channel support"""
pubsub = None
try:
# Create pubsub connection with resource monitoring
pubsub = redis_client.pubsub()
logger.debug(f"Created Redis pubsub connection for tenant: {tenant_id}")
# Monitor connection count
try:
connection_info = await redis_client.info('clients')
connected_clients = connection_info.get('connected_clients', 'unknown')
logger.debug(f"Redis connected clients: {connected_clients}")
except Exception:
# Don't fail if we can't get connection info
pass
# Determine which channels to subscribe to
subscription_channels = _get_subscription_channels(tenant_id, channel_filters)
@@ -460,10 +505,24 @@ async def events_stream(
except Exception as e:
logger.error(f"SSE error for tenant {tenant_id}: {e}", exc_info=True)
finally:
if pubsub:
await pubsub.unsubscribe()
await pubsub.close()
logger.info(f"SSE connection closed for tenant: {tenant_id}")
try:
if pubsub:
try:
# Unsubscribe from all channels
await pubsub.unsubscribe()
logger.debug(f"Unsubscribed from Redis channels for tenant: {tenant_id}")
except Exception as unsubscribe_error:
logger.error(f"Failed to unsubscribe Redis pubsub for tenant {tenant_id}: {unsubscribe_error}")
try:
# Close pubsub connection
await pubsub.close()
logger.debug(f"Closed Redis pubsub connection for tenant: {tenant_id}")
except Exception as close_error:
logger.error(f"Failed to close Redis pubsub for tenant {tenant_id}: {close_error}")
logger.info(f"SSE connection closed for tenant: {tenant_id}")
except Exception as finally_error:
logger.error(f"Error in SSE cleanup for tenant {tenant_id}: {finally_error}")
return StreamingResponse(
event_generator(),

View File

@@ -385,6 +385,12 @@ async def proxy_tenant_ingredients_base(request: Request, tenant_id: str = Path(
target_path = f"/api/v1/tenants/{tenant_id}/ingredients"
return await _proxy_to_inventory_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/ingredients/count", methods=["GET"])
async def proxy_tenant_ingredients_count(request: Request, tenant_id: str = Path(...)):
"""Proxy tenant ingredient count requests to inventory service"""
target_path = f"/api/v1/tenants/{tenant_id}/ingredients/count"
return await _proxy_to_inventory_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/ingredients/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
async def proxy_tenant_ingredients_with_path(request: Request, tenant_id: str = Path(...), path: str = ""):
"""Proxy tenant ingredient requests to inventory service (with additional path)"""
@@ -493,6 +499,12 @@ async def proxy_tenant_suppliers_base(request: Request, tenant_id: str = Path(..
target_path = f"/api/v1/tenants/{tenant_id}/suppliers"
return await _proxy_to_suppliers_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/suppliers/count", methods=["GET"])
async def proxy_tenant_suppliers_count(request: Request, tenant_id: str = Path(...)):
"""Proxy tenant supplier count requests to suppliers service"""
target_path = f"/api/v1/tenants/{tenant_id}/suppliers/count"
return await _proxy_to_suppliers_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/suppliers/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
async def proxy_tenant_suppliers_with_path(request: Request, tenant_id: str = Path(...), path: str = ""):
"""Proxy tenant supplier requests to suppliers service (with additional path)"""
@@ -545,6 +557,12 @@ async def proxy_tenant_recipes_base(request: Request, tenant_id: str = Path(...)
target_path = f"/api/v1/tenants/{tenant_id}/recipes"
return await _proxy_to_recipes_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/recipes/count", methods=["GET"])
async def proxy_tenant_recipes_count(request: Request, tenant_id: str = Path(...)):
"""Proxy tenant recipes count requests to recipes service"""
target_path = f"/api/v1/tenants/{tenant_id}/recipes/count"
return await _proxy_to_recipes_service(request, target_path, tenant_id=tenant_id)
@router.api_route("/{tenant_id}/recipes/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
async def proxy_tenant_recipes_with_path(request: Request, tenant_id: str = Path(...), path: str = ""):
"""Proxy tenant recipes requests to recipes service (with additional path)"""