feat: initialize XAS test console with native execution
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
"""Small SQLite repository. Queue transitions are serialized transactions."""
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timezone
|
||||
import json
|
||||
import sqlite3
|
||||
import uuid
|
||||
|
||||
TERMINAL = {"succeeded", "failed", "cancelled", "timed_out", "interrupted"}
|
||||
|
||||
|
||||
def now():
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
class Store:
|
||||
def __init__(self, settings):
|
||||
self.settings = settings
|
||||
settings.prepare()
|
||||
with self.connect() as db:
|
||||
version = db.execute("PRAGMA user_version").fetchone()[0]
|
||||
if version not in (0, 1):
|
||||
raise RuntimeError("Unsupported database version")
|
||||
db.executescript("""
|
||||
PRAGMA journal_mode=WAL;
|
||||
CREATE TABLE IF NOT EXISTS scripts(id TEXT PRIMARY KEY, data TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS versions(
|
||||
script_id TEXT NOT NULL, version INTEGER NOT NULL, data TEXT NOT NULL,
|
||||
PRIMARY KEY(script_id,version));
|
||||
CREATE TABLE IF NOT EXISTS runs(
|
||||
id TEXT PRIMARY KEY, request_id TEXT NOT NULL UNIQUE,
|
||||
script_id TEXT NOT NULL, state TEXT NOT NULL,
|
||||
cancel_requested INTEGER NOT NULL DEFAULT 0, data TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS health(key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
||||
PRAGMA user_version=1;
|
||||
""")
|
||||
|
||||
@contextmanager
|
||||
def connect(self, write=False):
|
||||
db = sqlite3.connect(self.settings.db, timeout=10)
|
||||
db.row_factory = sqlite3.Row
|
||||
try:
|
||||
if write:
|
||||
db.execute("BEGIN IMMEDIATE")
|
||||
yield db
|
||||
db.commit()
|
||||
except Exception:
|
||||
db.rollback()
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def scripts(self):
|
||||
with self.connect() as db:
|
||||
return [json.loads(r[0]) for r in db.execute("SELECT data FROM scripts ORDER BY id")]
|
||||
|
||||
def script(self, identity, versions=False):
|
||||
with self.connect() as db:
|
||||
row = db.execute("SELECT data FROM scripts WHERE id=?", (identity,)).fetchone()
|
||||
if not row:
|
||||
raise KeyError("脚本不存在")
|
||||
item = json.loads(row[0])
|
||||
if versions:
|
||||
item["versions"] = [json.loads(r[0]) for r in db.execute(
|
||||
"SELECT data FROM versions WHERE script_id=? ORDER BY version DESC", (identity,))]
|
||||
return item
|
||||
|
||||
def save_script(self, item):
|
||||
with self.connect(write=True) as db:
|
||||
db.execute("INSERT OR REPLACE INTO scripts VALUES(?,?)", (item["id"], json.dumps(item)))
|
||||
|
||||
def version(self, identity, version):
|
||||
with self.connect() as db:
|
||||
row = db.execute("SELECT data FROM versions WHERE script_id=? AND version=?", (identity, version)).fetchone()
|
||||
if not row:
|
||||
raise KeyError("版本不存在")
|
||||
return json.loads(row[0])
|
||||
|
||||
def upload_version(self, item, content, filename):
|
||||
# A transaction prevents two simultaneous uploads from assigning the same version.
|
||||
with self.connect(write=True) as db:
|
||||
current = db.execute("SELECT data FROM scripts WHERE id=?", (item["id"],)).fetchone()
|
||||
if current:
|
||||
item = json.loads(current[0])
|
||||
version = item["version"] + 1
|
||||
target = self.settings.library / item["id"] / "versions" / str(version) / filename
|
||||
target.parent.mkdir(parents=True, exist_ok=False)
|
||||
try:
|
||||
with target.open("xb") as handle:
|
||||
handle.write(content)
|
||||
import hashlib
|
||||
value = {"version": version, "sha256": hashlib.sha256(content).hexdigest(),
|
||||
"created_at": now(), "filename": filename, "path": str(target)}
|
||||
item.update(version=version, sha256=value["sha256"], source_path=str(target), updated_at=now())
|
||||
db.execute("INSERT INTO versions VALUES(?,?,?)", (item["id"], version, json.dumps(value)))
|
||||
db.execute("INSERT OR REPLACE INTO scripts VALUES(?,?)", (item["id"], json.dumps(item)))
|
||||
except Exception:
|
||||
target.unlink(missing_ok=True)
|
||||
target.parent.rmdir()
|
||||
raise
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def unpack_run(row):
|
||||
data = json.loads(row["data"])
|
||||
data.update(state=row["state"], cancel_requested=bool(row["cancel_requested"]))
|
||||
return data
|
||||
|
||||
def runs(self, script_id=None, limit=100):
|
||||
with self.connect() as db:
|
||||
query = "SELECT * FROM runs"
|
||||
values = []
|
||||
if script_id:
|
||||
query += " WHERE script_id=?"
|
||||
values.append(script_id)
|
||||
query += " ORDER BY rowid DESC LIMIT ?"
|
||||
values.append(limit)
|
||||
return [self.unpack_run(r) for r in db.execute(query, values)]
|
||||
|
||||
def run(self, identity):
|
||||
with self.connect() as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE id=?", (identity,)).fetchone()
|
||||
if not row:
|
||||
raise KeyError("运行记录不存在")
|
||||
return self.unpack_run(row)
|
||||
|
||||
def run_for_request(self, request_id):
|
||||
with self.connect() as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE request_id=?", (request_id,)).fetchone()
|
||||
return self.unpack_run(row) if row else None
|
||||
|
||||
def queue(self, script, request_id, ip, execution=None):
|
||||
with self.connect(write=True) as db:
|
||||
old = db.execute("SELECT * FROM runs WHERE request_id=?", (request_id,)).fetchone()
|
||||
if old:
|
||||
if old["script_id"] != script["id"]:
|
||||
raise ValueError("请求编号已用于其他脚本")
|
||||
return self.unpack_run(old)
|
||||
pending = db.execute("SELECT count(*) FROM runs WHERE state IN ('queued','running')").fetchone()[0]
|
||||
if pending >= 20:
|
||||
raise ValueError("队列已满,请等待已有测试结束")
|
||||
item = {"id": uuid.uuid4().hex, "script_id": script["id"], "script_name": script["name"],
|
||||
"version": script["version"], "sha256": script["sha256"], "state": "queued",
|
||||
"created_at": now(), "started_at": None, "finished_at": None, "exit_code": None,
|
||||
"message": "等待执行", "config": script["config"], "artifacts": [],
|
||||
"synthetic": script.get("synthetic", False), "source_ip": ip,
|
||||
"source_path": script["source_path"], "language": script["language"],
|
||||
"kind": script["kind"], "source_group": script["source_group"]}
|
||||
if execution:
|
||||
item["execution"] = execution
|
||||
db.execute("INSERT INTO runs(id,request_id,script_id,state,data) VALUES(?,?,?,?,?)",
|
||||
(item["id"], request_id, script["id"], "queued", json.dumps(item)))
|
||||
return item
|
||||
|
||||
def claim(self):
|
||||
with self.connect(write=True) as db:
|
||||
if db.execute("SELECT 1 FROM runs WHERE state='running'").fetchone():
|
||||
return None
|
||||
row = db.execute("SELECT * FROM runs WHERE state='queued' ORDER BY rowid LIMIT 1").fetchone()
|
||||
if not row:
|
||||
return None
|
||||
item = self.unpack_run(row)
|
||||
item.update(started_at=now(), state="running", message="正在启动测试")
|
||||
db.execute("UPDATE runs SET state='running',data=? WHERE id=?", (json.dumps(item), item["id"]))
|
||||
return item
|
||||
|
||||
def finish(self, identity, state, **values):
|
||||
assert state in TERMINAL
|
||||
with self.connect(write=True) as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE id=?", (identity,)).fetchone()
|
||||
item = self.unpack_run(row)
|
||||
item.update(values, state=state, finished_at=now())
|
||||
db.execute("UPDATE runs SET state=?,data=? WHERE id=?", (state, json.dumps(item), identity))
|
||||
return item
|
||||
|
||||
def running_message(self, identity, message):
|
||||
with self.connect(write=True) as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE id=? AND state='running'", (identity,)).fetchone()
|
||||
if row:
|
||||
item = self.unpack_run(row)
|
||||
item["message"] = message
|
||||
db.execute("UPDATE runs SET data=? WHERE id=?", (json.dumps(item), identity))
|
||||
|
||||
def update_execution(self, identity, values):
|
||||
with self.connect(write=True) as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE id=? AND state='running'", (identity,)).fetchone()
|
||||
if not row:
|
||||
raise ValueError("运行状态已变化")
|
||||
item = self.unpack_run(row)
|
||||
item["execution"] = {**item.get("execution", {}), **values}
|
||||
db.execute("UPDATE runs SET data=? WHERE id=?", (json.dumps(item), identity))
|
||||
return item
|
||||
|
||||
def cancel(self, identity):
|
||||
with self.connect(write=True) as db:
|
||||
row = db.execute("SELECT * FROM runs WHERE id=?", (identity,)).fetchone()
|
||||
if not row:
|
||||
raise KeyError("运行记录不存在")
|
||||
item = self.unpack_run(row)
|
||||
if row["state"] == "queued":
|
||||
item.update(state="cancelled", finished_at=now(), message="已取消排队")
|
||||
db.execute("UPDATE runs SET state='cancelled',cancel_requested=1,data=? WHERE id=?",
|
||||
(json.dumps(item), identity))
|
||||
elif row["state"] == "running":
|
||||
db.execute("UPDATE runs SET cancel_requested=1 WHERE id=?", (identity,))
|
||||
return self.run(identity)
|
||||
|
||||
def heartbeat(self):
|
||||
with self.connect(write=True) as db:
|
||||
db.execute("INSERT OR REPLACE INTO health VALUES('worker',?)", (now(),))
|
||||
|
||||
def health(self):
|
||||
with self.connect() as db:
|
||||
row = db.execute("SELECT value FROM health WHERE key='worker'").fetchone()
|
||||
last = row[0] if row else None
|
||||
online = bool(last and (datetime.now(timezone.utc) - datetime.fromisoformat(last)).total_seconds() < 15)
|
||||
return {"online": online, "last_seen": last}
|
||||
Reference in New Issue
Block a user