v1.0.92: Steps 5-9 — dynamic services, PostgreSQL history, /api/history
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Инициализация схемы БД — идемпотентно (CREATE IF NOT EXISTS).
|
||||
|
||||
Вызывается при старте приложения (в app.py, до register_blueprint).
|
||||
Безопасно для нескольких gunicorn-воркеров — PostgreSQL корректно
|
||||
обрабатывает конкурентный DDL.
|
||||
|
||||
Таблица runs — история запусков операций:
|
||||
- id, created_at
|
||||
- client_id, stand (изоляция по пользователю и стенду)
|
||||
- svc_id, svc_name, op_name, svc_op_id
|
||||
- instance_uid, display_name
|
||||
- status (OK/FAIL/TIMEOUT), duration_sec, error_log
|
||||
- params (JSONB), stages (JSONB)
|
||||
"""
|
||||
|
||||
import os
|
||||
from db.pool import get_conn, put_conn
|
||||
|
||||
SCHEMA_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS runs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
client_id VARCHAR(64) NOT NULL,
|
||||
stand VARCHAR(16) NOT NULL,
|
||||
svc_id INTEGER NOT NULL,
|
||||
svc_name VARCHAR(128),
|
||||
op_name VARCHAR(32) NOT NULL,
|
||||
svc_op_id INTEGER,
|
||||
instance_uid VARCHAR(64),
|
||||
display_name VARCHAR(255),
|
||||
status VARCHAR(16) NOT NULL,
|
||||
duration_sec DOUBLE PRECISION,
|
||||
error_log TEXT,
|
||||
params JSONB,
|
||||
stages JSONB
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_client_stand
|
||||
ON runs (client_id, stand, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_instance
|
||||
ON runs (instance_uid);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_runs_created
|
||||
ON runs (created_at);
|
||||
"""
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Применить схему — идемпотентно, безопасно для конкурентного вызова."""
|
||||
dsn = os.getenv("DATABASE_URL", "")
|
||||
if not dsn:
|
||||
print("[DB] DATABASE_URL not set — skipping init", flush=True)
|
||||
return
|
||||
|
||||
conn = get_conn()
|
||||
if not conn:
|
||||
print("[DB] Failed to get connection — skipping init", flush=True)
|
||||
return
|
||||
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(SCHEMA_SQL)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
print("[DB] Schema initialized", flush=True)
|
||||
except Exception as e:
|
||||
print(f"[DB] Init error: {e}", flush=True)
|
||||
conn.rollback()
|
||||
finally:
|
||||
put_conn(conn)
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
Connection pool для PostgreSQL (psycopg2).
|
||||
|
||||
Lazy-init: пул создаётся при первом обращении к БД в каждом gunicorn-воркере.
|
||||
Это безопасно для fork-модели — соединения открываются ПОСЛЕ fork.
|
||||
|
||||
DSN: из переменной окружения DATABASE_URL.
|
||||
Формат: postgresql://user:pass@host:5432/dbname?sslmode=require
|
||||
"""
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2 import pool
|
||||
|
||||
_pool = None
|
||||
|
||||
|
||||
def get_pool():
|
||||
"""Lazy-init ThreadedConnectionPool (1-5 соединений)."""
|
||||
global _pool
|
||||
if _pool is None:
|
||||
dsn = os.getenv("DATABASE_URL", "")
|
||||
if not dsn:
|
||||
return None
|
||||
_pool = pool.ThreadedConnectionPool(1, 5, dsn)
|
||||
return _pool
|
||||
|
||||
|
||||
def get_conn():
|
||||
"""Взять соединение из пула."""
|
||||
p = get_pool()
|
||||
return p.getconn() if p else None
|
||||
|
||||
|
||||
def put_conn(conn):
|
||||
"""Вернуть соединение в пул."""
|
||||
p = get_pool()
|
||||
if p and conn:
|
||||
p.putconn(conn)
|
||||
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
Сохранение результатов тестов в БД.
|
||||
"""
|
||||
|
||||
import json
|
||||
from db.pool import get_conn, put_conn
|
||||
|
||||
|
||||
def save_run(client_id, stand, svc_id, svc_name, op_name, svc_op_id,
|
||||
instance_uid, display_name, status, duration_sec,
|
||||
error_log, params, stages):
|
||||
"""Сохранить один запуск в таблицу runs."""
|
||||
conn = get_conn()
|
||||
if not conn:
|
||||
return
|
||||
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
INSERT INTO runs (client_id, stand, svc_id, svc_name, op_name, svc_op_id,
|
||||
instance_uid, display_name, status, duration_sec,
|
||||
error_log, params, stages)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""", (
|
||||
client_id, stand, svc_id, svc_name, op_name, svc_op_id,
|
||||
instance_uid, display_name, status, duration_sec,
|
||||
error_log,
|
||||
json.dumps(params) if params else None,
|
||||
json.dumps(stages) if stages else None,
|
||||
))
|
||||
conn.commit()
|
||||
cur.close()
|
||||
except Exception as e:
|
||||
print(f"[DB] save_run error: {e}", flush=True)
|
||||
conn.rollback()
|
||||
finally:
|
||||
put_conn(conn)
|
||||
Reference in New Issue
Block a user