agent: unify listening and dialing with directed mutual measurements
Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry. Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
This commit is contained in:
1 parent
d5ebf29e57
commit
ad5efc62ff
206 files changed
+4202
-4964
No files matched your search
@@ -2,267 +2,36 @@ using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace MoShiro.Backend.Storage.Sqlite;
|
||||
|
||||
/// <summary>表结构。版本记录在 <c>PRAGMA user_version</c>;以后的迁移是按版本号追加的内置 SQL。</summary>
|
||||
/// <summary>
|
||||
/// 表结构。版本记录在 <c>PRAGMA user_version</c>。当前只有一个版本:不是这个版本的数据库(包括更早的构建留下的)不会被迁移,
|
||||
/// 启动时报错,删除 <c>moshiro.db</c> 和 <c>raw/</c> 之后重新开始。以后的结构变更按版本号追加内置 SQL。
|
||||
/// </summary>
|
||||
internal static class Schema
|
||||
{
|
||||
public const int CurrentVersion = 16;
|
||||
public const int CurrentVersion = 1;
|
||||
|
||||
public static void Migrate(SqliteConnection connection)
|
||||
{
|
||||
int version = connection.Scalar<int>("PRAGMA user_version");
|
||||
if (version > CurrentVersion)
|
||||
if (version == CurrentVersion)
|
||||
{
|
||||
throw new InvalidOperationException($"database schema version {version} is newer than this build supports ({CurrentVersion})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (version < 1)
|
||||
if (version != 0)
|
||||
{
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec(V1);
|
||||
connection.Exec("PRAGMA user_version = 1");
|
||||
transaction.Commit();
|
||||
throw new InvalidOperationException($"database schema version {version} is not supported by this build (expected {CurrentVersion}); delete moshiro.db and the raw/ directory to start over");
|
||||
}
|
||||
|
||||
if (version < 2)
|
||||
{
|
||||
// 测试属于一条线路(Probe,Agent):旧测试没有 Probe,留空
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE tests ADD COLUMN probe_id TEXT NOT NULL DEFAULT ''");
|
||||
connection.Exec("CREATE INDEX ix_tests_line ON tests (agent_id, probe_id, requested_at)");
|
||||
connection.Exec("PRAGMA user_version = 2");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 3)
|
||||
{
|
||||
// 主机资源占用的历史(每个 Agent 每分钟一行)
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("""
|
||||
CREATE TABLE host_samples (
|
||||
agent_id TEXT NOT NULL,
|
||||
at INTEGER NOT NULL,
|
||||
cpu REAL,
|
||||
mem_used INTEGER,
|
||||
mem_total INTEGER,
|
||||
disk_used INTEGER,
|
||||
disk_total INTEGER,
|
||||
net_rx_bps INTEGER,
|
||||
net_tx_bps INTEGER,
|
||||
uptime INTEGER,
|
||||
PRIMARY KEY (agent_id, at)
|
||||
) WITHOUT ROWID;
|
||||
""");
|
||||
connection.Exec("PRAGMA user_version = 3");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 4)
|
||||
{
|
||||
// 节点的展示信息:系统、架构、国家、运营商;geo_host 是查询用的地址,只在 Backend 内部用
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("""
|
||||
CREATE TABLE node_meta (
|
||||
node_id TEXT PRIMARY KEY,
|
||||
kind TEXT NOT NULL,
|
||||
os TEXT,
|
||||
os_name TEXT,
|
||||
arch TEXT,
|
||||
country TEXT,
|
||||
isp_key TEXT,
|
||||
isp_name TEXT,
|
||||
geo_host TEXT,
|
||||
geo_at INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
) WITHOUT ROWID
|
||||
""");
|
||||
connection.Exec("PRAGMA user_version = 4");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 5)
|
||||
{
|
||||
// 节点自报的构建信息:版本、tag、提交、构建时间
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE node_meta ADD COLUMN version TEXT");
|
||||
connection.Exec("ALTER TABLE node_meta ADD COLUMN tag TEXT");
|
||||
connection.Exec("ALTER TABLE node_meta ADD COLUMN commit_id TEXT");
|
||||
connection.Exec("ALTER TABLE node_meta ADD COLUMN built_at TEXT");
|
||||
connection.Exec("PRAGMA user_version = 5");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 6)
|
||||
{
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN ipv6_host TEXT NOT NULL DEFAULT ''");
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN ipv6_supported INTEGER NOT NULL DEFAULT 0");
|
||||
connection.Exec("ALTER TABLE tests ADD COLUMN ip_version TEXT NOT NULL DEFAULT 'ipv4'");
|
||||
connection.Exec("CREATE INDEX ix_tests_path ON tests (agent_id, probe_id, ip_version, requested_at)");
|
||||
connection.Exec("PRAGMA user_version = 6");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 7)
|
||||
{
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN ipv6_diagnostics TEXT");
|
||||
connection.Exec("ALTER TABLE probes ADD COLUMN ipv6_diagnostics TEXT");
|
||||
connection.Exec("PRAGMA user_version = 7");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 8)
|
||||
{
|
||||
// Probe 用加入令牌注册:令牌带种类;Probe 有两把独立的密钥,对等面公钥单独存
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE join_tokens ADD COLUMN kind TEXT NOT NULL DEFAULT ''");
|
||||
connection.Exec("ALTER TABLE probes ADD COLUMN peer_pubkey BLOB");
|
||||
connection.Exec("PRAGMA user_version = 8");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 9)
|
||||
{
|
||||
// 对外地址自动识别:手填的标记为 manual,自动识别不会覆盖;Agent 上报的地址存下来,撤销手填时马上能重新识别
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN public_host_manual INTEGER NOT NULL DEFAULT 0");
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN ipv6_host_manual INTEGER NOT NULL DEFAULT 0");
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN reported_addresses TEXT NOT NULL DEFAULT '[]'");
|
||||
connection.Exec("PRAGMA user_version = 9");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 10)
|
||||
{
|
||||
// 预握手敲门:Probe 需要 Agent 的证书公钥(不只是指纹)来派生配对密钥,由 Agent 在 hello 里上报
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN tls_pubkey BLOB");
|
||||
connection.Exec("PRAGMA user_version = 10");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 11)
|
||||
{
|
||||
// 权限从三个作用域改成细粒度的能力。破坏性变更,不做兼容:旧的 API 令牌全部作废,需要重新创建
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("DROP TABLE api_tokens");
|
||||
connection.Exec("""
|
||||
CREATE TABLE api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
capabilities TEXT NOT NULL,
|
||||
token_sha256 BLOB NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_used_at INTEGER,
|
||||
expires_at INTEGER,
|
||||
revoked_at INTEGER
|
||||
)
|
||||
""");
|
||||
connection.Exec("PRAGMA user_version = 11");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 12)
|
||||
{
|
||||
// passkey 登录:每个用户可以注册若干 passkey(凭证公钥 SPKI、签名计数器);会话记录是怎么登录的,只有 passkey 登录的会话以后能提权
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("""
|
||||
CREATE TABLE passkeys (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
sign_count INTEGER NOT NULL DEFAULT 0,
|
||||
transports TEXT NOT NULL DEFAULT '[]',
|
||||
created_at INTEGER NOT NULL,
|
||||
last_used_at INTEGER
|
||||
)
|
||||
""");
|
||||
connection.Exec("CREATE INDEX ix_passkeys_user ON passkeys (user_id)");
|
||||
connection.Exec("ALTER TABLE sessions ADD COLUMN auth_method TEXT NOT NULL DEFAULT 'password'");
|
||||
connection.Exec("PRAGMA user_version = 12");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 13)
|
||||
{
|
||||
// 归属:加入码是谁创建的,节点是用哪个加入码(也就是谁)加进来的;审计日志
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE join_tokens ADD COLUMN created_by TEXT");
|
||||
connection.Exec("ALTER TABLE agents ADD COLUMN added_by TEXT");
|
||||
connection.Exec("ALTER TABLE probes ADD COLUMN added_by TEXT");
|
||||
connection.Exec("""
|
||||
CREATE TABLE audit_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
at INTEGER NOT NULL,
|
||||
user_id TEXT,
|
||||
actor TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target TEXT,
|
||||
detail TEXT,
|
||||
remote TEXT,
|
||||
success INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
connection.Exec("CREATE INDEX ix_audit_time ON audit_log (at)");
|
||||
connection.Exec(
|
||||
"""
|
||||
CREATE TABLE operator_sets (
|
||||
version INTEGER PRIMARY KEY,
|
||||
statement TEXT NOT NULL,
|
||||
credential_id TEXT NOT NULL,
|
||||
authenticator_data TEXT NOT NULL,
|
||||
client_data_json TEXT NOT NULL,
|
||||
signature TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
connection.Exec("PRAGMA user_version = 13");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 14)
|
||||
{
|
||||
// 节点的二进制绑定:第一次握手成功时绑定正在运行的二进制的 SHA-256
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec(
|
||||
"""
|
||||
CREATE TABLE node_bindings (
|
||||
node_id TEXT PRIMARY KEY,
|
||||
sha256 TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL,
|
||||
signed INTEGER NOT NULL,
|
||||
bound_at INTEGER NOT NULL,
|
||||
violation TEXT,
|
||||
violation_at INTEGER
|
||||
)
|
||||
""");
|
||||
connection.Exec("PRAGMA user_version = 14");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 15)
|
||||
{
|
||||
// 运行时二进制绑定整体移除(节点自报的哈希不可信,来源证明在安装时):旧版本留下的表不再使用
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("DROP TABLE IF EXISTS node_bindings");
|
||||
connection.Exec("PRAGMA user_version = 15");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
if (version < 16)
|
||||
{
|
||||
// 运营商可以手动指定(自动识别会错,例如 CN2 线路):手动的不会被后面的 IP 归属查询覆盖
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec("ALTER TABLE node_meta ADD COLUMN isp_manual INTEGER NOT NULL DEFAULT 0");
|
||||
connection.Exec("PRAGMA user_version = 16");
|
||||
transaction.Commit();
|
||||
}
|
||||
using SqliteTransaction transaction = connection.BeginTransaction();
|
||||
connection.Exec(V1);
|
||||
connection.Exec("PRAGMA user_version = 1");
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增 Kind 或指标都不需要改表结构:指标是 <c>metric_*.metrics</c> 里的一个键,Kind 只是 <c>raw_chunks.kind</c> 的一个取值。
|
||||
/// 节点只有一张表(<c>agents</c>):监听的节点有地址和端口,不监听的节点只拨出。线路 <c>(agent_id, series)</c> 里的 agent_id 是监听方,series 里是拨号方。
|
||||
/// </summary>
|
||||
private const string V1 = """
|
||||
CREATE TABLE agents (
|
||||
@@ -271,9 +40,17 @@ internal static class Schema
|
||||
status TEXT NOT NULL,
|
||||
pubkey BLOB NOT NULL,
|
||||
tls_spki BLOB NOT NULL,
|
||||
tls_pubkey BLOB NOT NULL,
|
||||
listens INTEGER NOT NULL DEFAULT 0,
|
||||
public_host TEXT NOT NULL DEFAULT '',
|
||||
probe_tcp_port INTEGER NOT NULL DEFAULT 7443,
|
||||
probe_udp_ports TEXT NOT NULL DEFAULT '[7443]',
|
||||
tcp_port INTEGER NOT NULL DEFAULT 0,
|
||||
quic_ports TEXT NOT NULL DEFAULT '[]',
|
||||
ipv6_host TEXT NOT NULL DEFAULT '',
|
||||
ipv6_supported INTEGER NOT NULL DEFAULT 0,
|
||||
ipv6_diagnostics TEXT,
|
||||
public_host_manual INTEGER NOT NULL DEFAULT 0,
|
||||
ipv6_host_manual INTEGER NOT NULL DEFAULT 0,
|
||||
reported_addresses TEXT NOT NULL DEFAULT '[]',
|
||||
labels TEXT NOT NULL DEFAULT '[]',
|
||||
public_visible INTEGER NOT NULL DEFAULT 1,
|
||||
kinds TEXT NOT NULL DEFAULT '[]',
|
||||
@@ -284,12 +61,67 @@ internal static class Schema
|
||||
rejected TEXT,
|
||||
task_hash TEXT,
|
||||
meta TEXT,
|
||||
added_by TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_seen_at INTEGER,
|
||||
row_version INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
-- 同名节点只允许一个已建立的(pending 的可以重名,等待管理员接管或改名)
|
||||
CREATE UNIQUE INDEX ux_agents_name ON agents (name) WHERE status <> 'pending';
|
||||
|
||||
CREATE TABLE analysis_queue (
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
window_start INTEGER NOT NULL,
|
||||
not_before INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (agent_id, series, window_start)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
capabilities TEXT NOT NULL,
|
||||
token_sha256 BLOB NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_used_at INTEGER,
|
||||
expires_at INTEGER,
|
||||
revoked_at INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE audit_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
at INTEGER NOT NULL,
|
||||
user_id TEXT,
|
||||
actor TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target TEXT,
|
||||
detail TEXT,
|
||||
remote TEXT,
|
||||
success INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE events (
|
||||
id INTEGER PRIMARY KEY,
|
||||
agent_id TEXT,
|
||||
kind TEXT NOT NULL,
|
||||
at INTEGER NOT NULL,
|
||||
detail TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE host_samples (
|
||||
agent_id TEXT NOT NULL,
|
||||
at INTEGER NOT NULL,
|
||||
cpu REAL,
|
||||
mem_used INTEGER,
|
||||
mem_total INTEGER,
|
||||
disk_used INTEGER,
|
||||
disk_total INTEGER,
|
||||
net_rx_bps INTEGER,
|
||||
net_tx_bps INTEGER,
|
||||
uptime INTEGER,
|
||||
PRIMARY KEY (agent_id, at)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE join_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -301,41 +133,105 @@ internal static class Schema
|
||||
uses INTEGER NOT NULL DEFAULT 0,
|
||||
expires_at INTEGER NOT NULL,
|
||||
revoked_at INTEGER,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE probes (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
pubkey BLOB NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
revoked_at INTEGER
|
||||
kind TEXT NOT NULL DEFAULT '',
|
||||
created_by TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash BLOB NOT NULL,
|
||||
password_salt BLOB NOT NULL,
|
||||
iterations INTEGER NOT NULL,
|
||||
CREATE TABLE metric_10s (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE metric_1h (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE metric_1m (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE metric_5m (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE metric_versions (
|
||||
metric_id TEXT PRIMARY KEY,
|
||||
fingerprint TEXT NOT NULL,
|
||||
recompute_from INTEGER,
|
||||
recompute_cursor INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE node_meta (
|
||||
node_id TEXT PRIMARY KEY,
|
||||
os TEXT,
|
||||
os_name TEXT,
|
||||
arch TEXT,
|
||||
country TEXT,
|
||||
isp_key TEXT,
|
||||
isp_name TEXT,
|
||||
isp_manual INTEGER NOT NULL DEFAULT 0,
|
||||
geo_host TEXT,
|
||||
geo_at INTEGER NOT NULL DEFAULT 0,
|
||||
version TEXT,
|
||||
tag TEXT,
|
||||
commit_id TEXT,
|
||||
built_at TEXT,
|
||||
updated_at INTEGER NOT NULL DEFAULT 0
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE operator_sets (
|
||||
version INTEGER PRIMARY KEY,
|
||||
statement TEXT NOT NULL,
|
||||
credential_id TEXT NOT NULL,
|
||||
authenticator_data TEXT NOT NULL,
|
||||
client_data_json TEXT NOT NULL,
|
||||
signature TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE passkeys (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
sign_count INTEGER NOT NULL DEFAULT 0,
|
||||
transports TEXT NOT NULL DEFAULT '[]',
|
||||
created_at INTEGER NOT NULL,
|
||||
last_used_at INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE raw_chunks (
|
||||
chunk_id TEXT PRIMARY KEY,
|
||||
segment_id INTEGER NOT NULL REFERENCES raw_segments(id),
|
||||
offset INTEGER NOT NULL,
|
||||
length INTEGER NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
task_id TEXT,
|
||||
test_id TEXT,
|
||||
phase INTEGER,
|
||||
start_ts INTEGER NOT NULL,
|
||||
duration_ms INTEGER NOT NULL,
|
||||
partial INTEGER NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE raw_segments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
day TEXT NOT NULL,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
bytes INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE rollup_dirty (
|
||||
level INTEGER NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
bucket_start INTEGER NOT NULL,
|
||||
PRIMARY KEY (level, agent_id, series, bucket_start)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE sessions (
|
||||
id_sha256 BLOB PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
csrf_token BLOB NOT NULL,
|
||||
auth_method TEXT NOT NULL DEFAULT 'password',
|
||||
created_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE api_tokens (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
scope TEXT NOT NULL,
|
||||
token_sha256 BLOB NOT NULL UNIQUE,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_used_at INTEGER,
|
||||
revoked_at INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
||||
|
||||
CREATE TABLE tasks (
|
||||
@@ -352,9 +248,17 @@ internal static class Schema
|
||||
row_version INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE test_results (
|
||||
test_id TEXT PRIMARY KEY,
|
||||
metrics BLOB NOT NULL,
|
||||
series BLOB NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE tests (
|
||||
id TEXT PRIMARY KEY,
|
||||
agent_id TEXT NOT NULL,
|
||||
dialer_id TEXT NOT NULL,
|
||||
ip_version TEXT NOT NULL DEFAULT 'ipv4',
|
||||
plan TEXT NOT NULL,
|
||||
params TEXT NOT NULL,
|
||||
trigger TEXT NOT NULL,
|
||||
@@ -366,79 +270,42 @@ internal static class Schema
|
||||
started_at INTEGER,
|
||||
finished_at INTEGER
|
||||
);
|
||||
CREATE INDEX ix_tests_agent ON tests (agent_id, requested_at);
|
||||
CREATE TABLE test_results (
|
||||
test_id TEXT PRIMARY KEY,
|
||||
metrics BLOB NOT NULL,
|
||||
series BLOB NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE raw_segments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
day TEXT NOT NULL,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
bytes INTEGER NOT NULL DEFAULT 0
|
||||
CREATE TABLE users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash BLOB NOT NULL,
|
||||
password_salt BLOB NOT NULL,
|
||||
iterations INTEGER NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE raw_chunks (
|
||||
chunk_id TEXT PRIMARY KEY,
|
||||
segment_id INTEGER NOT NULL REFERENCES raw_segments(id),
|
||||
offset INTEGER NOT NULL,
|
||||
length INTEGER NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
task_id TEXT,
|
||||
test_id TEXT,
|
||||
phase INTEGER,
|
||||
start_ts INTEGER NOT NULL,
|
||||
duration_ms INTEGER NOT NULL,
|
||||
partial INTEGER NOT NULL
|
||||
) WITHOUT ROWID;
|
||||
CREATE INDEX ix_raw_series ON raw_chunks (agent_id, series, start_ts);
|
||||
CREATE INDEX ix_raw_test ON raw_chunks (test_id) WHERE test_id IS NOT NULL;
|
||||
CREATE INDEX ix_raw_segment ON raw_chunks (segment_id);
|
||||
|
||||
CREATE TABLE metric_10s (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
CREATE TABLE metric_1m (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
CREATE TABLE metric_5m (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
CREATE TABLE metric_1h (agent_id TEXT NOT NULL, series TEXT NOT NULL, window_start INTEGER NOT NULL, partial INTEGER NOT NULL, metrics BLOB NOT NULL, PRIMARY KEY (agent_id, series, window_start)) WITHOUT ROWID;
|
||||
CREATE INDEX ix_audit_time ON audit_log (at);
|
||||
|
||||
CREATE INDEX ix_events_agent ON events (agent_id, at);
|
||||
|
||||
CREATE INDEX ix_events_time ON events (at);
|
||||
|
||||
CREATE INDEX ix_metric_10s_time ON metric_10s (window_start);
|
||||
|
||||
CREATE INDEX ix_metric_1m_time ON metric_1m (window_start);
|
||||
|
||||
CREATE INDEX ix_metric_5m_time ON metric_5m (window_start);
|
||||
|
||||
CREATE TABLE metric_versions (
|
||||
metric_id TEXT PRIMARY KEY,
|
||||
fingerprint TEXT NOT NULL,
|
||||
recompute_from INTEGER,
|
||||
recompute_cursor INTEGER
|
||||
);
|
||||
CREATE INDEX ix_passkeys_user ON passkeys (user_id);
|
||||
|
||||
CREATE TABLE analysis_queue (
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
window_start INTEGER NOT NULL,
|
||||
not_before INTEGER NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (agent_id, series, window_start)
|
||||
) WITHOUT ROWID;
|
||||
CREATE INDEX ix_queue_due ON analysis_queue (not_before);
|
||||
CREATE TABLE rollup_dirty (
|
||||
level INTEGER NOT NULL,
|
||||
agent_id TEXT NOT NULL,
|
||||
series TEXT NOT NULL,
|
||||
bucket_start INTEGER NOT NULL,
|
||||
PRIMARY KEY (level, agent_id, series, bucket_start)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE events (
|
||||
id INTEGER PRIMARY KEY,
|
||||
agent_id TEXT,
|
||||
kind TEXT NOT NULL,
|
||||
at INTEGER NOT NULL,
|
||||
detail TEXT
|
||||
);
|
||||
CREATE INDEX ix_events_time ON events (at);
|
||||
CREATE INDEX ix_events_agent ON events (agent_id, at);
|
||||
CREATE INDEX ix_raw_segment ON raw_chunks (segment_id);
|
||||
|
||||
CREATE INDEX ix_raw_series ON raw_chunks (agent_id, series, start_ts);
|
||||
|
||||
CREATE INDEX ix_raw_test ON raw_chunks (test_id) WHERE test_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX ix_tests_agent ON tests (agent_id, requested_at);
|
||||
|
||||
CREATE INDEX ix_tests_path ON tests (agent_id, dialer_id, ip_version, requested_at);
|
||||
|
||||
CREATE UNIQUE INDEX ux_agents_name ON agents (name) WHERE status <> 'pending';
|
||||
""";
|
||||
}
|
||||
Reference in new issue
Block a user