驱动接入(全部本机真实例验收): - Elasticsearch 原生 SQL API、InfluxDB 原生 HTTP+Flux(官方 JDBC 付费许可,实测弃用) - ZooKeeper/MongoDB JVM agent(连接时自动下载官方驱动 jar,sha256 钉版本到 ~/.opsxdb/drivers/) - 达梦 JDBC agent 桥就绪(待厂商 jar+实例);TiDB 真实例验收 - 协议族代码审查放行 PolarDB/TDSQL/OceanBase/Kingbase/GaussDB/Oracle - 驱动能力模型:按驱动开关对象树分组/右键菜单/行编辑,NoSQL 树拍平(对象直挂库节点) 驱动管理: - 驱动管理弹框(状态/预装/离线包导入/JDK 检测)+ /api/drivers/* 三接口 - opsxdb-driver 驱动商店仓库(清单/fetch.sh/pack.sh 离线包/import.sh) - 连接时懒加载:DMG 零 Java 产物,首连自动下载(实测 ZK 九件套 1.35s),离线包自动回退 UI/交互: - 设置中心(⚙):常规/执行/结果/驱动/快捷键/关于,左导航+右面板(对标 opsxshell) - 实例级展开箭头(行首 SVG chevron,替代字体回退失真的 Unicode 三角) - 树内嵌套渲染修复:对象树挂在激活连接下;工具栏窄窗口不断行(按钮 nowrap+整颗换行) - 侧栏连接行精简(图标+连接名)、SQLite SSH 守卫、Oracle 死参数清理 质量: - Go 协议层/离线回退单测、E2E 门控测试(ES/ZK/Influx/Mongo/TiDB/JDBC 桥) - 驱动注册表单一事实来源 connection-types.json,README/BACKLOG/AGENTS 同步
314 lines
10 KiB
Bash
Executable File
314 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# OpsxDB 驱动验收:一次只起一个测试库(见 README.md)。
|
||
# ./verify-crud.sh # 按 sqlite → a → b → c → f → d → e 顺序,每步只留一个容器
|
||
# ./verify-crud.sh a # 只验 MySQL
|
||
# ./verify-crud.sh f # 只验 Redis
|
||
set -uo pipefail
|
||
API="${OPSXDB_API:-http://127.0.0.1:8788}"
|
||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
cd "$(dirname "$0")"
|
||
REPORT="$ROOT/tests/accept-result.txt"
|
||
: >"$REPORT"
|
||
|
||
n_ok=0; n_fail=0
|
||
ok() { echo " OK $*"; echo " OK $*" >>"$REPORT"; n_ok=$((n_ok+1)); }
|
||
bad() { echo " FAIL $*"; echo " FAIL $*" >>"$REPORT"; n_fail=$((n_fail+1)); }
|
||
log() { echo "$*"; echo "$*" >>"$REPORT"; }
|
||
|
||
post() {
|
||
curl -sS -m 45 -H 'Content-Type: application/json' -d "$1" "$API$2"
|
||
}
|
||
|
||
json_ok() {
|
||
python3 -c 'import json,sys
|
||
d=json.load(sys.stdin)
|
||
if d.get("needConfirm"): sys.exit(2)
|
||
err=d.get("error")
|
||
if err: sys.exit(1)
|
||
if "ok" in d and d.get("ok") is False: sys.exit(1)
|
||
sys.exit(0)'
|
||
}
|
||
|
||
wait_health() {
|
||
local name="$1" tries="${2:-60}"
|
||
for i in $(seq 1 "$tries"); do
|
||
local st
|
||
st=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)
|
||
if [[ "$st" == "healthy" ]]; then return 0; fi
|
||
if [[ "$st" == "running" && "$tries" -lt 5 ]]; then return 0; fi
|
||
sleep 2
|
||
done
|
||
echo " (health=$st after ${tries} tries)"
|
||
[[ "$st" == "running" || "$st" == "healthy" ]]
|
||
}
|
||
|
||
assert_single_container() {
|
||
local expect="$1"
|
||
local running
|
||
running=$(docker ps --filter name=opsxdb- --format '{{.Names}}' | grep -v '^$' || true)
|
||
local n
|
||
n=$(echo "$running" | grep -c . || true)
|
||
if [[ -z "$expect" ]]; then
|
||
[[ "$n" -eq 0 ]] && ok "无测试库容器" || bad "仍有容器: $running"
|
||
return
|
||
fi
|
||
if [[ "$n" -eq 1 ]] && echo "$running" | grep -qx "$expect"; then
|
||
ok "仅 $expect 在跑"
|
||
else
|
||
bad "期望仅 ${expect},实际: ${running:-none}"
|
||
fi
|
||
}
|
||
|
||
up_one() {
|
||
local alias="$1" container="$2" tries="${3:-40}"
|
||
./dbctl.sh up "$alias"
|
||
wait_health "$container" "$tries" || true
|
||
assert_single_container "$container"
|
||
}
|
||
|
||
exec_sql() {
|
||
local base="$1" sql="$2"
|
||
python3 -c 'import json,sys
|
||
d=json.loads(sys.argv[1]); d["sql"]=sys.argv[2]; print(json.dumps(d))' "$base" "$sql"
|
||
}
|
||
|
||
crud_sql() {
|
||
local driver="$1" host="$2" port="$3" user="$4" password="$5" db="$6" dialect="$7"
|
||
local base
|
||
base=$(printf '{"driver":"%s","host":"%s","port":%s,"user":"%s","password":"%s","database":"%s"}' \
|
||
"$driver" "$host" "$port" "$user" "$password" "$db")
|
||
|
||
log "== $driver =="
|
||
local tc
|
||
tc=$(post "$base" /api/db/test-connection)
|
||
echo "$tc" | json_ok && ok "test-connection" || { bad "test-connection $tc"; return; }
|
||
|
||
local dbs
|
||
dbs=$(post "$base" /api/db/databases)
|
||
echo "$dbs" | json_ok && ok "databases" || bad "databases $dbs"
|
||
|
||
local tables
|
||
tables=$(post "$base" /api/db/tables)
|
||
echo "$tables" | json_ok && ok "tables" || bad "tables $tables"
|
||
|
||
local sel ins upd del
|
||
case "$dialect" in
|
||
mysql|postgres|sqlite)
|
||
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
|
||
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
|
||
upd="UPDATE users SET status=0 WHERE name='grace'"
|
||
del="DELETE FROM users WHERE name='grace'"
|
||
;;
|
||
mssql)
|
||
sel='SELECT TOP 5 id,name FROM dbo.users ORDER BY id'
|
||
ins="INSERT INTO dbo.users (name, email, status) VALUES (N'grace',N'grace@demo.local',1)"
|
||
upd="UPDATE dbo.users SET status=0 WHERE name=N'grace'"
|
||
del="DELETE FROM dbo.users WHERE name=N'grace'"
|
||
;;
|
||
oracle)
|
||
sel='SELECT id,name FROM users ORDER BY id FETCH FIRST 5 ROWS ONLY'
|
||
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
|
||
upd="UPDATE users SET status=0 WHERE name='grace'"
|
||
del="DELETE FROM users WHERE name='grace'"
|
||
;;
|
||
redis)
|
||
sel='PING'
|
||
ins='SET opsxdb:demo:grace hello'
|
||
upd='GET opsxdb:demo:grace'
|
||
del='DEL opsxdb:demo:grace'
|
||
;;
|
||
*)
|
||
bad "unknown dialect $dialect"; return
|
||
;;
|
||
esac
|
||
|
||
local body r
|
||
body=$(exec_sql "$base" "$sel")
|
||
r=$(post "$body" /api/db/exec)
|
||
echo "$r" | json_ok && ok "SELECT/PING" || bad "SELECT/PING $r"
|
||
|
||
body=$(exec_sql "$base" "$ins")
|
||
r=$(post "$body" /api/db/exec)
|
||
echo "$r" | json_ok && ok "INSERT/SET" || bad "INSERT/SET $r"
|
||
|
||
body=$(exec_sql "$base" "$upd")
|
||
r=$(post "$body" /api/db/exec)
|
||
echo "$r" | json_ok && ok "UPDATE/GET" || bad "UPDATE/GET $r"
|
||
|
||
body=$(exec_sql "$base" "$del")
|
||
r=$(post "$body" /api/db/exec)
|
||
echo "$r" | json_ok && ok "DELETE/DEL" || bad "DELETE/DEL $r"
|
||
|
||
if [[ "$dialect" == "redis" ]]; then
|
||
local keys kv scan
|
||
keys=$(post "$base" /api/db/redis/keys)
|
||
echo "$keys" | json_ok && ok "redis/keys" || bad "redis/keys $keys"
|
||
body=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["sql"]="SET CEX:FOLLOW:k1 v"; print(json.dumps(d))' "$base")
|
||
post "$body" /api/db/exec >/dev/null
|
||
kv=$(python3 -c 'import json,sys; d=json.loads(sys.argv[1]); d["key"]="CEX:FOLLOW:k1"; print(json.dumps(d))' "$base")
|
||
kv=$(post "$kv" /api/db/redis/key)
|
||
echo "$kv" | json_ok && ok "redis/key" || bad "redis/key $kv"
|
||
body=$(exec_sql "$base" "SCAN 0 MATCH CEX:* COUNT 50")
|
||
scan=$(post "$body" /api/db/exec)
|
||
echo "$scan" | json_ok && ok "SCAN MATCH" || bad "SCAN MATCH $scan"
|
||
fi
|
||
}
|
||
|
||
run_sqlite() {
|
||
log
|
||
log "---- sqlite(无 Docker)----"
|
||
local SQLITE_DB="$ROOT/tests/tmp/demo.sqlite"
|
||
mkdir -p "$ROOT/tests/tmp"
|
||
rm -f "$SQLITE_DB"
|
||
if ! command -v sqlite3 >/dev/null 2>&1; then
|
||
bad "本机无 sqlite3"
|
||
return
|
||
fi
|
||
sqlite3 "$SQLITE_DB" < init/sqlite/01_demo.sql
|
||
crud_sql sqlite "" 0 "" "" "$SQLITE_DB" sqlite
|
||
}
|
||
|
||
run_mysql() {
|
||
log
|
||
log "---- a MySQL :3306 ----"
|
||
up_one a opsxdb-mysql 40
|
||
docker exec -i opsxdb-mysql mysql -uroot -p'Demo@123' < init/mysql/01_demo.sql 2>/dev/null
|
||
crud_sql mysql 127.0.0.1 3306 root 'Demo@123' demo mysql
|
||
}
|
||
|
||
run_mariadb() {
|
||
log
|
||
log "---- b MariaDB :3307 ----"
|
||
up_one b opsxdb-mariadb 40
|
||
docker exec -i opsxdb-mariadb mariadb -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null \
|
||
|| docker exec -i opsxdb-mariadb mysql -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null
|
||
crud_sql mariadb 127.0.0.1 3307 root 'Demo@123' demo mysql
|
||
}
|
||
|
||
run_postgres() {
|
||
log
|
||
log "---- c PostgreSQL :5432 ----"
|
||
up_one c opsxdb-postgres 40
|
||
docker exec -i opsxdb-postgres psql -U opsx -d demo < init/postgres/01_demo.sql >/dev/null
|
||
crud_sql postgresql 127.0.0.1 5432 opsx 'Demo@123' demo postgres
|
||
}
|
||
|
||
run_redis() {
|
||
log
|
||
log "---- f Redis :6379 ----"
|
||
up_one f opsxdb-redis 30
|
||
docker exec opsxdb-redis redis-cli SET LANG:zh-CN hello >/dev/null
|
||
docker exec opsxdb-redis redis-cli HSET CEX:FOLLOW:FOLLOWERS:1 f1 '{"a":1}' >/dev/null
|
||
crud_sql redis 127.0.0.1 6379 "" "" 0 redis
|
||
}
|
||
|
||
run_mssql() {
|
||
log
|
||
log "---- d SQL Server :1433 ----"
|
||
up_one d opsxdb-mssql 120
|
||
local ready=0
|
||
for i in $(seq 1 60); do
|
||
if docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' >/dev/null 2>&1 \
|
||
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -Q 'SELECT 1' >/dev/null 2>&1; then
|
||
ready=1
|
||
break
|
||
fi
|
||
sleep 3
|
||
done
|
||
if [[ "$ready" -ne 1 ]]; then
|
||
bad "sqlcmd 未就绪"
|
||
return
|
||
fi
|
||
ok "sqlcmd ready"
|
||
docker cp init/mssql/01_demo.sql opsxdb-mssql:/tmp/01_demo.sql
|
||
docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -i /tmp/01_demo.sql >/dev/null 2>&1 \
|
||
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -i /tmp/01_demo.sql >/dev/null 2>&1 \
|
||
|| true
|
||
local base_master='{"driver":"sqlserver","host":"127.0.0.1","port":1433,"user":"sa","password":"Demo@12345","database":"master"}'
|
||
local r
|
||
r=$(post "$base_master" /api/db/test-connection)
|
||
echo "$r" | json_ok && ok "test-connection master" || bad "test-connection master $r"
|
||
crud_sql sqlserver 127.0.0.1 1433 sa 'Demo@12345' demo mssql
|
||
}
|
||
|
||
run_oracle() {
|
||
log
|
||
log "---- e Oracle :1521(Apple Silicon 常 ORA-00442)----"
|
||
up_one e opsxdb-oracle 180
|
||
docker cp init/oracle/01_demo.sql opsxdb-oracle:/tmp/01_demo.sql 2>/dev/null || true
|
||
docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s opsx/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|
||
|| docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s system/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" >/dev/null 2>&1 \
|
||
|| true
|
||
crud_sql oracle 127.0.0.1 1521 opsx 'Demo@123' XEPDB1 oracle
|
||
}
|
||
|
||
run_grey() {
|
||
local alias="$1" name="$2" container="$3" driver="$4" port="$5"
|
||
log
|
||
log "---- $alias $name(驱动未接入,只验测连失败/灰态)----"
|
||
up_one "$alias" "$container" 40
|
||
local base
|
||
base=$(printf '{"driver":"%s","host":"127.0.0.1","port":%s}' "$driver" "$port")
|
||
local r
|
||
r=$(post "$base" /api/db/test-connection)
|
||
if echo "$r" | grep -qiE '未接入|未就绪|not ready|unknown driver|unsupported'; then
|
||
ok "灰态测连被拒绝"
|
||
else
|
||
echo "$r" | json_ok && bad "未接入驱动却测连成功 $r" || ok "测连失败(驱动未接入): $r"
|
||
fi
|
||
}
|
||
|
||
usage() {
|
||
cat <<USAGE
|
||
用法: $0 [目标]
|
||
|
||
无参数 顺序验收 sqlite a b c f d e(每步只留一个容器)
|
||
sqlite 仅 SQLite 文件库
|
||
a|mysql MySQL :3306
|
||
b|mariadb MariaDB :3307
|
||
c|postgres PostgreSQL :5432
|
||
d|mssql SQL Server :1433
|
||
e|oracle Oracle :1521
|
||
f|redis Redis :6379
|
||
g|h|i ZooKeeper / ES / Influx(驱动未接入,只验灰态)
|
||
|
||
结果写入 tests/accept-result.txt
|
||
USAGE
|
||
}
|
||
|
||
log "API=$API $(date '+%Y-%m-%d %H:%M:%S')"
|
||
if ! curl -sS -m 5 "$API/api/health" >/dev/null; then
|
||
log "API down — 请先在仓库根目录 ./run.sh web"
|
||
exit 1
|
||
fi
|
||
ok "API health"
|
||
|
||
target="${1:-all}"
|
||
case "$target" in
|
||
-h|--help|help) usage; exit 0 ;;
|
||
sqlite) run_sqlite ;;
|
||
a|mysql) run_mysql ;;
|
||
b|mariadb|maria) run_mariadb ;;
|
||
c|postgres|pg|postgresql) run_postgres ;;
|
||
d|mssql|sqlserver) run_mssql ;;
|
||
e|oracle) run_oracle ;;
|
||
f|redis) run_redis ;;
|
||
g|zk|zookeeper) run_grey g ZooKeeper opsxdb-zookeeper zookeeper 2181 ;;
|
||
h|es|elasticsearch) run_grey h Elasticsearch opsxdb-elasticsearch elasticsearch 9200 ;;
|
||
i|influx|influxdb) run_grey i InfluxDB opsxdb-influxdb influxdb 8086 ;;
|
||
all|"")
|
||
run_sqlite
|
||
run_mysql
|
||
run_mariadb
|
||
run_postgres
|
||
run_redis
|
||
run_mssql
|
||
run_oracle
|
||
;;
|
||
*) usage; exit 1 ;;
|
||
esac
|
||
|
||
log
|
||
log "==== SUMMARY ok=$n_ok fail=$n_fail ===="
|
||
[[ "$n_fail" -eq 0 ]]
|