- Add test environment to __init__.py for all eveai services
- Add postgresql certificate to secrets for secure communication in staging and production environments - Adapt for TLS communication with PostgreSQL - Adapt tasks to handle invalid connections from the connection pool - Migrate to psycopg3 for connection to PostgreSQL
This commit is contained in:
@@ -12,6 +12,7 @@ class MinioClient:
|
|||||||
self.client = None
|
self.client = None
|
||||||
|
|
||||||
def init_app(self, app: Flask):
|
def init_app(self, app: Flask):
|
||||||
|
app.logger.debug(f"Initializing MinIO client with endpoint: {app.config['MINIO_ENDPOINT']} and secure: {app.config.get('MINIO_USE_HTTPS', False)}")
|
||||||
self.client = Minio(
|
self.client = Minio(
|
||||||
app.config['MINIO_ENDPOINT'],
|
app.config['MINIO_ENDPOINT'],
|
||||||
access_key=app.config['MINIO_ACCESS_KEY'],
|
access_key=app.config['MINIO_ACCESS_KEY'],
|
||||||
|
|||||||
@@ -23,9 +23,40 @@ class Config(object):
|
|||||||
DB_PASS = environ.get('DB_PASS')
|
DB_PASS = environ.get('DB_PASS')
|
||||||
DB_NAME = environ.get('DB_NAME')
|
DB_NAME = environ.get('DB_NAME')
|
||||||
DB_PORT = environ.get('DB_PORT')
|
DB_PORT = environ.get('DB_PORT')
|
||||||
SQLALCHEMY_DATABASE_URI = f'postgresql+pg8000://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
|
SQLALCHEMY_DATABASE_URI = f'postgresql+psycopg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
|
||||||
SQLALCHEMY_BINDS = {'public': SQLALCHEMY_DATABASE_URI}
|
SQLALCHEMY_BINDS = {'public': SQLALCHEMY_DATABASE_URI}
|
||||||
|
|
||||||
|
# Database Engine Options (health checks and keepalives)
|
||||||
|
PGSQL_CERT_DATA = environ.get('PGSQL_CERT')
|
||||||
|
PGSQL_CA_CERT_PATH = None
|
||||||
|
if PGSQL_CERT_DATA:
|
||||||
|
_tmp = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.pem')
|
||||||
|
_tmp.write(PGSQL_CERT_DATA)
|
||||||
|
_tmp.flush()
|
||||||
|
_tmp.close()
|
||||||
|
PGSQL_CA_CERT_PATH = _tmp.name
|
||||||
|
|
||||||
|
# Psycopg3 connect args (libpq parameters)
|
||||||
|
_CONNECT_ARGS = {
|
||||||
|
'connect_timeout': 5,
|
||||||
|
'keepalives': 1,
|
||||||
|
'keepalives_idle': 60,
|
||||||
|
'keepalives_interval': 30,
|
||||||
|
'keepalives_count': 5,
|
||||||
|
}
|
||||||
|
if PGSQL_CA_CERT_PATH:
|
||||||
|
_CONNECT_ARGS.update({
|
||||||
|
'sslmode': 'require',
|
||||||
|
'sslrootcert': PGSQL_CA_CERT_PATH,
|
||||||
|
})
|
||||||
|
|
||||||
|
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||||
|
'pool_pre_ping': True,
|
||||||
|
'pool_recycle': 180,
|
||||||
|
'pool_use_lifo': True,
|
||||||
|
'connect_args': _CONNECT_ARGS,
|
||||||
|
}
|
||||||
|
|
||||||
# Redis Settings ------------------------------------------------------------------------------
|
# Redis Settings ------------------------------------------------------------------------------
|
||||||
REDIS_URL = environ.get('REDIS_URL')
|
REDIS_URL = environ.get('REDIS_URL')
|
||||||
REDIS_PORT = environ.get('REDIS_PORT', '6379')
|
REDIS_PORT = environ.get('REDIS_PORT', '6379')
|
||||||
@@ -342,9 +373,38 @@ class DevConfig(Config):
|
|||||||
# PATH settings
|
# PATH settings
|
||||||
ffmpeg_path = '/usr/bin/ffmpeg'
|
ffmpeg_path = '/usr/bin/ffmpeg'
|
||||||
|
|
||||||
|
# OBJECT STORAGE
|
||||||
|
OBJECT_STORAGE_TYPE = 'MINIO'
|
||||||
|
OBJECT_STORAGE_TENANT_BASE = 'folder'
|
||||||
|
OBJECT_STORAGE_BUCKET_NAME = ('eveai-tenants')
|
||||||
|
# MINIO
|
||||||
|
MINIO_ENDPOINT = 'minio:9000'
|
||||||
|
MINIO_ACCESS_KEY = 'minioadmin'
|
||||||
|
MINIO_SECRET_KEY = 'minioadmin'
|
||||||
|
MINIO_USE_HTTPS = False
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(Config):
|
||||||
|
DEVELOPMENT = True
|
||||||
|
DEBUG = True
|
||||||
|
FLASK_DEBUG = True
|
||||||
|
EXPLAIN_TEMPLATE_LOADING = False
|
||||||
|
|
||||||
|
# Define the nginx prefix used for the specific apps
|
||||||
|
EVEAI_APP_LOCATION_PREFIX = ''
|
||||||
|
EVEAI_CHAT_LOCATION_PREFIX = '/chat'
|
||||||
|
CHAT_CLIENT_PREFIX = 'chat-client/chat/'
|
||||||
|
|
||||||
|
# Define the static path
|
||||||
|
STATIC_URL = 'https://evie-staging-static.askeveai.com'
|
||||||
|
|
||||||
|
# PATH settings
|
||||||
|
ffmpeg_path = '/usr/bin/ffmpeg'
|
||||||
|
|
||||||
# OBJECT STORAGE
|
# OBJECT STORAGE
|
||||||
OBJECT_STORAGE_TYPE = 'MINIO'
|
OBJECT_STORAGE_TYPE = 'MINIO'
|
||||||
OBJECT_STORAGE_TENANT_BASE = 'bucket'
|
OBJECT_STORAGE_TENANT_BASE = 'bucket'
|
||||||
|
OBJECT_STORAGE_BUCKET_NAME = 'main'
|
||||||
# MINIO
|
# MINIO
|
||||||
MINIO_ENDPOINT = 'minio:9000'
|
MINIO_ENDPOINT = 'minio:9000'
|
||||||
MINIO_ACCESS_KEY = 'minioadmin'
|
MINIO_ACCESS_KEY = 'minioadmin'
|
||||||
@@ -411,6 +471,7 @@ class ProdConfig(Config):
|
|||||||
def get_config(config_name='dev'):
|
def get_config(config_name='dev'):
|
||||||
configs = {
|
configs = {
|
||||||
'dev': DevConfig,
|
'dev': DevConfig,
|
||||||
|
'test': TestConfig,
|
||||||
'staging': StagingConfig,
|
'staging': StagingConfig,
|
||||||
'prod': ProdConfig,
|
'prod': ProdConfig,
|
||||||
'default': DevConfig,
|
'default': DevConfig,
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
case 'staging':
|
case 'staging':
|
||||||
app.config.from_object(get_config('staging'))
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
case 'staging':
|
case 'staging':
|
||||||
app.config.from_object(get_config('staging'))
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
|
case 'staging':
|
||||||
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
app.config.from_object(get_config('prod'))
|
app.config.from_object(get_config('prod'))
|
||||||
case _:
|
case _:
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
|
case 'staging':
|
||||||
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
app.config.from_object(get_config('prod'))
|
app.config.from_object(get_config('prod'))
|
||||||
case _:
|
case _:
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ from typing import Dict, Any, Optional
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from celery import states
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError, InterfaceError, OperationalError
|
||||||
|
|
||||||
from common.utils.config_field_types import TaggingFields
|
from common.utils.config_field_types import TaggingFields
|
||||||
from common.utils.database import Database
|
from common.utils.database import Database
|
||||||
@@ -214,7 +215,7 @@ def prepare_arguments(specialist: Any, arguments: Dict[str, Any]) -> Dict[str, A
|
|||||||
raise ArgumentPreparationError(str(e))
|
raise ArgumentPreparationError(str(e))
|
||||||
|
|
||||||
|
|
||||||
@current_celery.task(name='execute_specialist', queue='llm_interactions', bind=True)
|
@current_celery.task(bind=True, name='execute_specialist', queue='llm_interactions', autoretry_for=(InterfaceError, OperationalError), retry_backoff=True, retry_jitter=True, max_retries=5)
|
||||||
def execute_specialist(self, tenant_id: int, specialist_id: int, arguments: Dict[str, Any],
|
def execute_specialist(self, tenant_id: int, specialist_id: int, arguments: Dict[str, Any],
|
||||||
session_id: str, user_timezone: str) -> dict:
|
session_id: str, user_timezone: str) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -356,6 +357,7 @@ def execute_specialist(self, tenant_id: int, specialist_id: int, arguments: Dict
|
|||||||
stacktrace = traceback.format_exc()
|
stacktrace = traceback.format_exc()
|
||||||
current_app.logger.error(f'execute_specialist: Error updating interaction: {e}\n{stacktrace}')
|
current_app.logger.error(f'execute_specialist: Error updating interaction: {e}\n{stacktrace}')
|
||||||
|
|
||||||
|
self.update_state(state=states.FAILURE)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
|
case 'staging':
|
||||||
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
app.config.from_object(get_config('prod'))
|
app.config.from_object(get_config('prod'))
|
||||||
case _:
|
case _:
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import os
|
|||||||
from datetime import datetime as dt, timezone as tz, datetime
|
from datetime import datetime as dt, timezone as tz, datetime
|
||||||
|
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from celery import states
|
||||||
from sqlalchemy import or_, and_, text
|
from sqlalchemy import or_, and_, text
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError, InterfaceError, OperationalError
|
||||||
from common.extensions import db
|
from common.extensions import db
|
||||||
from common.models.user import Tenant
|
from common.models.user import Tenant
|
||||||
from common.models.entitlements import BusinessEventLog, LicenseUsage, License
|
from common.models.entitlements import BusinessEventLog, LicenseUsage, License
|
||||||
@@ -20,8 +21,8 @@ def ping():
|
|||||||
return 'pong'
|
return 'pong'
|
||||||
|
|
||||||
|
|
||||||
@current_celery.task(name='persist_business_events', queue='entitlements')
|
@current_celery.task(bind=True, name='persist_business_events', queue='entitlements', autoretry_for=(InterfaceError, OperationalError), retry_backoff=True, retry_jitter=True, max_retries=5)
|
||||||
def persist_business_events(log_entries):
|
def persist_business_events(self, log_entries):
|
||||||
"""
|
"""
|
||||||
Persist multiple business event logs to the database in a single transaction
|
Persist multiple business event logs to the database in a single transaction
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
case 'staging':
|
case 'staging':
|
||||||
app.config.from_object(get_config('staging'))
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
|
|||||||
13
eveai_ops/healthz.py
Normal file
13
eveai_ops/healthz.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from flask import Blueprint, jsonify
|
||||||
|
|
||||||
|
healthz_bp = Blueprint('ops_healthz', __name__)
|
||||||
|
|
||||||
|
@healthz_bp.route('/healthz/live', methods=['GET'])
|
||||||
|
def live():
|
||||||
|
# Minimal liveness: process is running
|
||||||
|
return jsonify(status='ok'), 200
|
||||||
|
|
||||||
|
@healthz_bp.route('/healthz/ready', methods=['GET'])
|
||||||
|
def ready():
|
||||||
|
# Minimal readiness for now (no external checks)
|
||||||
|
return jsonify(status='ready'), 200
|
||||||
@@ -17,6 +17,10 @@ def create_app(config_file=None):
|
|||||||
match environment:
|
match environment:
|
||||||
case 'development':
|
case 'development':
|
||||||
app.config.from_object(get_config('dev'))
|
app.config.from_object(get_config('dev'))
|
||||||
|
case 'test':
|
||||||
|
app.config.from_object(get_config('test'))
|
||||||
|
case 'staging':
|
||||||
|
app.config.from_object(get_config('staging'))
|
||||||
case 'production':
|
case 'production':
|
||||||
app.config.from_object(get_config('prod'))
|
app.config.from_object(get_config('prod'))
|
||||||
case _:
|
case _:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from langchain_core.output_parsers import StrOutputParser
|
|||||||
from langchain_core.prompts import ChatPromptTemplate
|
from langchain_core.prompts import ChatPromptTemplate
|
||||||
from langchain_core.runnables import RunnablePassthrough
|
from langchain_core.runnables import RunnablePassthrough
|
||||||
from sqlalchemy import or_
|
from sqlalchemy import or_
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError, InterfaceError, OperationalError
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from common.extensions import db, cache_manager
|
from common.extensions import db, cache_manager
|
||||||
@@ -37,8 +37,10 @@ def ping():
|
|||||||
return 'pong'
|
return 'pong'
|
||||||
|
|
||||||
|
|
||||||
@current_celery.task(name='create_embeddings', queue='embeddings')
|
@current_celery.task(bind=True, name='create_embeddings', queue='embeddings',
|
||||||
def create_embeddings(tenant_id, document_version_id):
|
autoretry_for=(InterfaceError, OperationalError),
|
||||||
|
retry_backoff=True, retry_jitter=True, max_retries=5)
|
||||||
|
def create_embeddings(self, tenant_id, document_version_id):
|
||||||
document_version = None
|
document_version = None
|
||||||
try:
|
try:
|
||||||
# Retrieve Tenant for which we are processing
|
# Retrieve Tenant for which we are processing
|
||||||
@@ -127,7 +129,7 @@ def create_embeddings(tenant_id, document_version_id):
|
|||||||
document_version.processing_finished_at = dt.now(tz.utc)
|
document_version.processing_finished_at = dt.now(tz.utc)
|
||||||
document_version.processing_error = str(e)[:255]
|
document_version.processing_error = str(e)[:255]
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
create_embeddings.update_state(state=states.FAILURE)
|
self.update_state(state=states.FAILURE)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ langchain-text-splitters~=0.3.10
|
|||||||
langcodes~=3.4.0
|
langcodes~=3.4.0
|
||||||
langdetect~=1.0.9
|
langdetect~=1.0.9
|
||||||
openai~=1.102.0
|
openai~=1.102.0
|
||||||
pg8000~=1.31.2
|
psycopg[binary]~=3.2
|
||||||
pgvector~=0.2.5
|
pgvector~=0.2.5
|
||||||
pycryptodome~=3.20.0
|
pycryptodome~=3.20.0
|
||||||
pydantic>=2.10.3,<3
|
pydantic>=2.10.3,<3
|
||||||
|
|||||||
@@ -39,3 +39,6 @@ spec:
|
|||||||
- secretKey: REDIS_CERT
|
- secretKey: REDIS_CERT
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: name:eveai-redis-certificate
|
key: name:eveai-redis-certificate
|
||||||
|
- secretKey: PGSQL_CERT
|
||||||
|
remoteRef:
|
||||||
|
key: name:eveai-postgresql-certificate
|
||||||
|
|||||||
Reference in New Issue
Block a user