This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git
The following commit(s) were added to refs/heads/master by this push:
new 89624809f0 Migrate e2e admin-API curl calls to swctl admin commands
(#13889)
89624809f0 is described below
commit 89624809f078b6424b474323ae091f72b9e694b4
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Thu Jun 4 09:18:58 2026 +0800
Migrate e2e admin-API curl calls to swctl admin commands (#13889)
* Migrate e2e admin-API curl calls to swctl admin commands
Replace raw curl interactions with the OAP admin-server REST host (port
17128)
across the e2e suite with the new 'swctl admin ...' command tree
(skywalking-cli #228, commit b447211):
- runtime-rule flows (mal/lal/cluster) -> swctl admin runtime-rule ...
- dsl-debug flows (oal/mal/lal-block/lal-statement) -> swctl admin
dsl-debug ...
- ui-management -> swctl admin ui-template ...
- storage inspect/config(dump,ttl) -> swctl admin inspect|config ...
--display json/yaml keeps response bodies parseable by the existing jq/yq
assertions; runtime-rule negative paths assert the CLI's typed error
envelope
(HTTP <code> (<applyStatus>)). config-dump.yml is regenerated as the JSON
string-map that 'admin config dump' returns. Bumps SW_CTL_COMMIT to the
admin-capable commit. Non-admin curls (BanyanDB probe, self-telemetry, alarm
webhook) are intentionally kept.
* e2e: fix swctl admin review feedback (yaml.v2 key casing + missing swctl
install)
- inspect cases: the CLI marshals typed structs through yaml.v2 (json tags
ignored -> lowercased keys scopeid/entityid/mqeentity), which would not
match
the existing fixtures (scopeId/entityId/mqeEntity). Use '--display json |
yq -P'
so the json tags are honored and the fixtures stay unchanged.
- runtime-rule/cluster and ui-management/banyandb setups now install swctl:
both
cases newly depend on it (the flows/cases no longer use curl), but their
setup
only installed jq / yq, so a clean e2e shell would hit 'swctl: command
not found'.
---
.../dsl-debugging/lal-block/dsl-debug-flow.sh | 30 +-
test/e2e-v2/cases/dsl-debugging/lal-block/e2e.yaml | 2 +-
.../dsl-debugging/lal-statement/dsl-debug-flow.sh | 30 +-
.../cases/dsl-debugging/lal-statement/e2e.yaml | 2 +-
.../cases/dsl-debugging/mal/dsl-debug-flow.sh | 29 +-
test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml | 2 +-
.../cases/dsl-debugging/oal/dsl-debug-flow.sh | 19 +-
.../cases/runtime-rule/cluster/cluster-flow.sh | 25 +-
test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml | 4 +-
test/e2e-v2/cases/runtime-rule/lal/e2e.yaml | 2 +-
test/e2e-v2/cases/runtime-rule/lal/lal-flow.sh | 27 +-
.../runtime-rule/mal-storage/banyandb/e2e.yaml | 2 +-
.../mal-storage/elasticsearch/e2e.yaml | 2 +-
.../runtime-rule/mal-storage/postgresql/e2e.yaml | 2 +-
.../runtime-rule/mal-storage/runtime-rule-flow.sh | 180 +++++----
test/e2e-v2/cases/storage/banyandb/e2e.yaml | 2 +-
test/e2e-v2/cases/storage/es/e2e.yaml | 2 +-
test/e2e-v2/cases/storage/es/es-sharding/e2e.yaml | 2 +-
test/e2e-v2/cases/storage/expected/config-dump.yml | 428 +++++++++++----------
test/e2e-v2/cases/storage/mysql/e2e.yaml | 5 +-
test/e2e-v2/cases/storage/opensearch/e2e.yaml | 2 +-
test/e2e-v2/cases/storage/postgres/e2e.yaml | 2 +-
test/e2e-v2/cases/storage/storage-cases.yaml | 35 +-
test/e2e-v2/cases/ui-management/banyandb/e2e.yaml | 4 +-
.../cases/ui-management/ui-management-cases.yaml | 69 ++--
test/e2e-v2/script/env | 2 +-
26 files changed, 475 insertions(+), 436 deletions(-)
diff --git a/test/e2e-v2/cases/dsl-debugging/lal-block/dsl-debug-flow.sh
b/test/e2e-v2/cases/dsl-debugging/lal-block/dsl-debug-flow.sh
index b22cd0fbfc..ceeff95a2b 100755
--- a/test/e2e-v2/cases/dsl-debugging/lal-block/dsl-debug-flow.sh
+++ b/test/e2e-v2/cases/dsl-debugging/lal-block/dsl-debug-flow.sh
@@ -39,6 +39,11 @@ OAP_HOST="${OAP_HOST:-127.0.0.1}"
OAP_REST_PORT="${OAP_REST_PORT:-17128}"
OAP_BASE="http://${OAP_HOST}:${OAP_REST_PORT}"
+# All admin-server REST calls go through swctl's `admin` command tree instead
of
+# raw curl. `--display json` keeps the body shape identical to the old curl
+# output, so the downstream jq assertions are unchanged.
+admin() { swctl --display json --admin-url="${OAP_BASE}" admin "$@"; }
+
SETTLE_SECONDS="${SETTLE_SECONDS:-180}"
# Reuse the same multi-statement seed the statement case uses, so comparing
@@ -57,27 +62,25 @@ CLIENT_ID="e2e-dsldbg-lal-block-1"
log "waiting for OAP admin port"
deadline=$(( $(date +%s) + 90 ))
-until curl -fsS "${OAP_BASE}/dsl-debugging/status" >/dev/null 2>&1; do
+until admin dsl-debug status >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP admin not ready
after 90s"; fi
sleep 2
done
# --- Phase 0: status check
------------------------------------------------------------
-log "=== Phase 0: /dsl-debugging/status ==="
-status_body="$(curl -fsS "${OAP_BASE}/dsl-debugging/status")"
+log "=== Phase 0: dsl-debug status ==="
+status_body="$(admin dsl-debug status)"
echo "${status_body}" | jq -e '.injectionEnabled == true' >/dev/null \
|| fail "injectionEnabled is not true"
# --- Phase 1: apply runtime-rule LAL
--------------------------------------------------
log "=== Phase 1: apply runtime-rule LAL seed (shared with lal-statement case)
==="
-curl -fsS -XPOST -H 'Content-Type: text/plain' \
- --data-binary "@${SEED_LAL}" \
-
"${OAP_BASE}/runtime/rule/addOrUpdate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null \
+admin runtime-rule add --catalog "${RR_CATALOG}" --name "${RR_NAME}" -f
"${SEED_LAL}" >/dev/null \
|| fail "addOrUpdate ${RR_CATALOG}/${RR_NAME} failed"
deadline=$(( $(date +%s) + 60 ))
status=""
while (( $(date +%s) < deadline )); do
- status="$(curl -fsS "${OAP_BASE}/runtime/rule/list" 2>/dev/null \
+ status="$(admin runtime-rule list 2>/dev/null \
| jq -r --arg c "${RR_CATALOG}" --arg n "${RR_NAME}" \
'.rules[] | select(.catalog == $c and .name == $n) | .status' |
head -1)"
[ "${status}" = "ACTIVE" ] && break
@@ -88,8 +91,9 @@ log "✓ seed applied: ${RR_CATALOG}/${RR_NAME} ACTIVE"
# --- Phase 2: install with granularity=block (default)
--------------------------------
log "=== Phase 2: install session with granularity=block ==="
-install_body="$(curl -fsS -XPOST \
-
"${OAP_BASE}/dsl-debugging/session?catalog=${DBG_CATALOG}&name=${DBG_NAME}&ruleName=${DBG_RULE_NAME}&clientId=${CLIENT_ID}&granularity=block")"
+install_body="$(admin dsl-debug session start \
+ --catalog "${DBG_CATALOG}" --name "${DBG_NAME}" --rule-name
"${DBG_RULE_NAME}" \
+ --client-id "${CLIENT_ID}" --granularity block)"
log " install → ${install_body}"
SESSION_ID="$(echo "${install_body}" | jq -r '.sessionId // empty')"
[ -n "${SESSION_ID}" ] || fail "install did not return sessionId"
@@ -103,7 +107,7 @@ deadline=$(( $(date +%s) + SETTLE_SECONDS ))
records_count=0
collect_body=""
while (( $(date +%s) < deadline )); do
- collect_body="$(curl -fsS
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}")"
+ collect_body="$(admin dsl-debug session get "${SESSION_ID}")"
records_count="$(echo "${collect_body}" | jq '[.nodes[].records[]] |
length')"
if [ "${records_count}" -gt 0 ]; then
# Block mode: a fully walked record has at minimum input + output
samples.
@@ -160,13 +164,13 @@ log "✓ block-mode shape valid (${records_count} records;
per-statement probes
# --- Phase 6: stop session
------------------------------------------------------------
log "=== Phase 6: stop session ==="
-stop_body="$(curl -fsS -XPOST
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}/stop")"
+stop_body="$(admin dsl-debug session stop "${SESSION_ID}")"
echo "${stop_body}" | jq -e '.localStopped == true' >/dev/null \
|| fail "localStopped != true"
# --- Phase 7: cleanup runtime-rule
----------------------------------------------------
log "=== Phase 7: cleanup runtime-rule ==="
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/inactivate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/delete?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
+admin runtime-rule inactivate --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
+admin runtime-rule delete --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
log "=== ALL LAL BLOCK FLOW PHASES PASSED ==="
diff --git a/test/e2e-v2/cases/dsl-debugging/lal-block/e2e.yaml
b/test/e2e-v2/cases/dsl-debugging/lal-block/e2e.yaml
index c7afbc6009..7d655867d1 100644
--- a/test/e2e-v2/cases/dsl-debugging/lal-block/e2e.yaml
+++ b/test/e2e-v2/cases/dsl-debugging/lal-block/e2e.yaml
@@ -52,7 +52,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/dsl-debugging/status >/dev/null
&& echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
dsl-debug status >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/dsl-debugging/lal-statement/dsl-debug-flow.sh
b/test/e2e-v2/cases/dsl-debugging/lal-statement/dsl-debug-flow.sh
index 6ab7dcaea2..ff24bd6e1a 100755
--- a/test/e2e-v2/cases/dsl-debugging/lal-statement/dsl-debug-flow.sh
+++ b/test/e2e-v2/cases/dsl-debugging/lal-statement/dsl-debug-flow.sh
@@ -35,6 +35,11 @@ OAP_HOST="${OAP_HOST:-127.0.0.1}"
OAP_REST_PORT="${OAP_REST_PORT:-17128}"
OAP_BASE="http://${OAP_HOST}:${OAP_REST_PORT}"
+# All admin-server REST calls go through swctl's `admin` command tree instead
of
+# raw curl. `--display json` keeps the body shape identical to the old curl
+# output, so the downstream jq assertions are unchanged.
+admin() { swctl --display json --admin-url="${OAP_BASE}" admin "$@"; }
+
SETTLE_SECONDS="${SETTLE_SECONDS:-180}"
SEED_DIR="${SEED_DIR:-$(pwd)/test/e2e-v2/cases/dsl-debugging/lal-statement/seed-rules}"
@@ -50,27 +55,25 @@ CLIENT_ID="e2e-dsldbg-lal-stmt-1"
log "waiting for OAP admin port"
deadline=$(( $(date +%s) + 90 ))
-until curl -fsS "${OAP_BASE}/dsl-debugging/status" >/dev/null 2>&1; do
+until admin dsl-debug status >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP admin not ready
after 90s"; fi
sleep 2
done
# --- Phase 0: status check
------------------------------------------------------------
-log "=== Phase 0: /dsl-debugging/status ==="
-status_body="$(curl -fsS "${OAP_BASE}/dsl-debugging/status")"
+log "=== Phase 0: dsl-debug status ==="
+status_body="$(admin dsl-debug status)"
echo "${status_body}" | jq -e '.injectionEnabled == true' >/dev/null \
|| fail "injectionEnabled is not true"
# --- Phase 1: apply runtime-rule LAL
--------------------------------------------------
log "=== Phase 1: apply runtime-rule LAL seed ==="
-curl -fsS -XPOST -H 'Content-Type: text/plain' \
- --data-binary "@${SEED_LAL}" \
-
"${OAP_BASE}/runtime/rule/addOrUpdate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null \
+admin runtime-rule add --catalog "${RR_CATALOG}" --name "${RR_NAME}" -f
"${SEED_LAL}" >/dev/null \
|| fail "addOrUpdate ${RR_CATALOG}/${RR_NAME} failed"
deadline=$(( $(date +%s) + 60 ))
status=""
while (( $(date +%s) < deadline )); do
- status="$(curl -fsS "${OAP_BASE}/runtime/rule/list" 2>/dev/null \
+ status="$(admin runtime-rule list 2>/dev/null \
| jq -r --arg c "${RR_CATALOG}" --arg n "${RR_NAME}" \
'.rules[] | select(.catalog == $c and .name == $n) | .status' |
head -1)"
[ "${status}" = "ACTIVE" ] && break
@@ -81,8 +84,9 @@ log "✓ seed applied: ${RR_CATALOG}/${RR_NAME} ACTIVE"
# --- Phase 2: install with granularity=statement
--------------------------------------
log "=== Phase 2: install session with granularity=statement ==="
-install_body="$(curl -fsS -XPOST \
-
"${OAP_BASE}/dsl-debugging/session?catalog=${DBG_CATALOG}&name=${DBG_NAME}&ruleName=${DBG_RULE_NAME}&clientId=${CLIENT_ID}&granularity=statement")"
+install_body="$(admin dsl-debug session start \
+ --catalog "${DBG_CATALOG}" --name "${DBG_NAME}" --rule-name
"${DBG_RULE_NAME}" \
+ --client-id "${CLIENT_ID}" --granularity statement)"
log " install → ${install_body}"
SESSION_ID="$(echo "${install_body}" | jq -r '.sessionId // empty')"
[ -n "${SESSION_ID}" ] || fail "install did not return sessionId"
@@ -96,7 +100,7 @@ deadline=$(( $(date +%s) + SETTLE_SECONDS ))
records_count=0
collect_body=""
while (( $(date +%s) < deadline )); do
- collect_body="$(curl -fsS
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}")"
+ collect_body="$(admin dsl-debug session get "${SESSION_ID}")"
records_count="$(echo "${collect_body}" | jq '[.nodes[].records[]] |
length')"
if [ "${records_count}" -gt 0 ]; then
# Wait for a record with several samples — statement mode produces one
@@ -162,13 +166,13 @@ log "✓ statement-mode shape valid (${records_count}
records, ${distinct} disti
# --- Phase 6: stop session
------------------------------------------------------------
log "=== Phase 6: stop session ==="
-stop_body="$(curl -fsS -XPOST
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}/stop")"
+stop_body="$(admin dsl-debug session stop "${SESSION_ID}")"
echo "${stop_body}" | jq -e '.localStopped == true' >/dev/null \
|| fail "localStopped != true"
# --- Phase 7: cleanup runtime-rule
----------------------------------------------------
log "=== Phase 7: cleanup runtime-rule ==="
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/inactivate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/delete?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
+admin runtime-rule inactivate --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
+admin runtime-rule delete --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
log "=== ALL LAL STATEMENT FLOW PHASES PASSED ==="
diff --git a/test/e2e-v2/cases/dsl-debugging/lal-statement/e2e.yaml
b/test/e2e-v2/cases/dsl-debugging/lal-statement/e2e.yaml
index 23fa7b7700..ee84a35c7d 100644
--- a/test/e2e-v2/cases/dsl-debugging/lal-statement/e2e.yaml
+++ b/test/e2e-v2/cases/dsl-debugging/lal-statement/e2e.yaml
@@ -51,7 +51,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/dsl-debugging/status >/dev/null
&& echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
dsl-debug status >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/dsl-debugging/mal/dsl-debug-flow.sh
b/test/e2e-v2/cases/dsl-debugging/mal/dsl-debug-flow.sh
index 267234d760..7b2b051ebb 100755
--- a/test/e2e-v2/cases/dsl-debugging/mal/dsl-debug-flow.sh
+++ b/test/e2e-v2/cases/dsl-debugging/mal/dsl-debug-flow.sh
@@ -37,6 +37,11 @@ OAP_HOST="${OAP_HOST:-127.0.0.1}"
OAP_REST_PORT="${OAP_REST_PORT:-17128}"
OAP_BASE="http://${OAP_HOST}:${OAP_REST_PORT}"
+# All admin-server REST calls go through swctl's `admin` command tree instead
of
+# raw curl. `--display json` keeps the body shape identical to the old curl
+# output, so the downstream jq assertions are unchanged.
+admin() { swctl --display json --admin-url="${OAP_BASE}" admin "$@"; }
+
SETTLE_SECONDS="${SETTLE_SECONDS:-300}"
SEED_DIR="${SEED_DIR:-$(pwd)/test/e2e-v2/cases/dsl-debugging/mal/seed-rules}"
@@ -53,26 +58,24 @@ CLIENT_ID="e2e-dsldbg-mal-1"
log "waiting for OAP admin port"
deadline=$(( $(date +%s) + 120 ))
-until curl -fsS "${OAP_BASE}/dsl-debugging/status" >/dev/null 2>&1; do
+until admin dsl-debug status >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP admin not ready
after 120s"; fi
sleep 2
done
# --- Phase 0: status
------------------------------------------------------------------
-log "=== Phase 0: /dsl-debugging/status ==="
-curl -fsS "${OAP_BASE}/dsl-debugging/status" | jq -e '.injectionEnabled ==
true' >/dev/null \
+log "=== Phase 0: dsl-debug status ==="
+admin dsl-debug status | jq -e '.injectionEnabled == true' >/dev/null \
|| fail "injectionEnabled is not true"
# --- Phase 1: apply runtime-rule MAL
--------------------------------------------------
log "=== Phase 1: apply runtime-rule MAL seed ==="
-curl -fsS -XPOST -H 'Content-Type: text/plain' \
- --data-binary "@${SEED_MAL}" \
-
"${OAP_BASE}/runtime/rule/addOrUpdate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null \
+admin runtime-rule add --catalog "${RR_CATALOG}" --name "${RR_NAME}" -f
"${SEED_MAL}" >/dev/null \
|| fail "addOrUpdate ${RR_CATALOG}/${RR_NAME} failed"
deadline=$(( $(date +%s) + 60 ))
status=""
while (( $(date +%s) < deadline )); do
- status="$(curl -fsS "${OAP_BASE}/runtime/rule/list" 2>/dev/null \
+ status="$(admin runtime-rule list 2>/dev/null \
| jq -r --arg c "${RR_CATALOG}" --arg n "${RR_NAME}" \
'.rules[] | select(.catalog == $c and .name == $n) | .status' |
head -1)"
[ "${status}" = "ACTIVE" ] && break
@@ -83,8 +86,8 @@ log "✓ seed applied: ${RR_CATALOG}/${RR_NAME} ACTIVE"
# --- Phase 2: install session
---------------------------------------------------------
log "=== Phase 2: install session on (${DBG_CATALOG}, ${DBG_NAME},
${DBG_RULE_NAME}) ==="
-install_body="$(curl -fsS -XPOST \
-
"${OAP_BASE}/dsl-debugging/session?catalog=${DBG_CATALOG}&name=${DBG_NAME}&ruleName=${DBG_RULE_NAME}&clientId=${CLIENT_ID}")"
+install_body="$(admin dsl-debug session start \
+ --catalog "${DBG_CATALOG}" --name "${DBG_NAME}" --rule-name
"${DBG_RULE_NAME}" --client-id "${CLIENT_ID}")"
log " install → ${install_body}"
SESSION_ID="$(echo "${install_body}" | jq -r '.sessionId // empty')"
[ -n "${SESSION_ID}" ] || fail "install did not return sessionId — body:
${install_body}"
@@ -96,7 +99,7 @@ deadline=$(( $(date +%s) + SETTLE_SECONDS ))
records_count=0
collect_body=""
while (( $(date +%s) < deadline )); do
- collect_body="$(curl -fsS
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}")"
+ collect_body="$(admin dsl-debug session get "${SESSION_ID}")"
records_count="$(echo "${collect_body}" | jq '[.nodes[].records[]] |
length')"
if [ "${records_count}" -gt 0 ]; then
# Wait for at least one full execution (terminal meterEmit closed it),
@@ -218,13 +221,13 @@ log "✓ MAL shape valid (${records_count} records,
${total_samples} samples)"
# --- Phase 6: stop session
------------------------------------------------------------
log "=== Phase 6: stop session ==="
-curl -fsS -XPOST "${OAP_BASE}/dsl-debugging/session/${SESSION_ID}/stop" \
+admin dsl-debug session stop "${SESSION_ID}" \
| jq -e '.localStopped == true' >/dev/null \
|| fail "localStopped != true"
# --- Phase 7: cleanup runtime-rule
----------------------------------------------------
log "=== Phase 7: cleanup runtime-rule ==="
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/inactivate?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
-curl -fsS -XPOST
"${OAP_BASE}/runtime/rule/delete?catalog=${RR_CATALOG}&name=${RR_NAME}"
>/dev/null || true
+admin runtime-rule inactivate --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
+admin runtime-rule delete --catalog "${RR_CATALOG}" --name "${RR_NAME}"
>/dev/null || true
log "=== ALL MAL FLOW PHASES PASSED ==="
diff --git a/test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml
b/test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml
index c0fe353257..d2c9dec2d4 100644
--- a/test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml
+++ b/test/e2e-v2/cases/dsl-debugging/mal/e2e.yaml
@@ -50,7 +50,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/dsl-debugging/status >/dev/null
&& echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
dsl-debug status >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/dsl-debugging/oal/dsl-debug-flow.sh
b/test/e2e-v2/cases/dsl-debugging/oal/dsl-debug-flow.sh
index 80027e186c..240b3c99f3 100755
--- a/test/e2e-v2/cases/dsl-debugging/oal/dsl-debug-flow.sh
+++ b/test/e2e-v2/cases/dsl-debugging/oal/dsl-debug-flow.sh
@@ -38,6 +38,11 @@ OAP_HOST="${OAP_HOST:-127.0.0.1}"
OAP_REST_PORT="${OAP_REST_PORT:-17128}"
OAP_BASE="http://${OAP_HOST}:${OAP_REST_PORT}"
+# All admin-server REST calls go through swctl's `admin` command tree instead
of
+# raw curl. `--display json` keeps the body shape identical to the old curl
+# output, so the downstream jq assertions are unchanged.
+admin() { swctl --display json --admin-url="${OAP_BASE}" admin "$@"; }
+
SETTLE_SECONDS="${SETTLE_SECONDS:-300}"
# OAL gate is per-metric. We target service_relation_server_cpm — a shipped
@@ -51,20 +56,20 @@ CLIENT_ID="e2e-dsldbg-oal-1"
log "waiting for OAP admin port"
deadline=$(( $(date +%s) + 120 ))
-until curl -fsS "${OAP_BASE}/dsl-debugging/status" >/dev/null 2>&1; do
+until admin dsl-debug status >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP admin not ready
after 120s"; fi
sleep 2
done
# --- Phase 0: status
------------------------------------------------------------------
-log "=== Phase 0: /dsl-debugging/status ==="
-curl -fsS "${OAP_BASE}/dsl-debugging/status" | jq -e '.injectionEnabled ==
true' >/dev/null \
+log "=== Phase 0: dsl-debug status ==="
+admin dsl-debug status | jq -e '.injectionEnabled == true' >/dev/null \
|| fail "injectionEnabled is not true"
# --- Phase 1: install session
---------------------------------------------------------
log "=== Phase 1: install session on (${CATALOG}, ${NAME}, ${METRIC}) ==="
-install_body="$(curl -fsS -XPOST \
-
"${OAP_BASE}/dsl-debugging/session?catalog=${CATALOG}&name=${NAME}&ruleName=${METRIC}&clientId=${CLIENT_ID}")"
+install_body="$(admin dsl-debug session start \
+ --catalog "${CATALOG}" --name "${NAME}" --rule-name "${METRIC}"
--client-id "${CLIENT_ID}")"
log " install → ${install_body}"
SESSION_ID="$(echo "${install_body}" | jq -r '.sessionId // empty')"
[ -n "${SESSION_ID}" ] || fail "install did not return sessionId — body:
${install_body}"
@@ -76,7 +81,7 @@ deadline=$(( $(date +%s) + SETTLE_SECONDS ))
records_count=0
collect_body=""
while (( $(date +%s) < deadline )); do
- collect_body="$(curl -fsS
"${OAP_BASE}/dsl-debugging/session/${SESSION_ID}")"
+ collect_body="$(admin dsl-debug session get "${SESSION_ID}")"
records_count="$(echo "${collect_body}" | jq '[.nodes[].records[]] |
length')"
if [ "${records_count}" -gt 0 ]; then
# Wait for at least one execution record with both source + filter
samples.
@@ -197,7 +202,7 @@ log "✓ OAL shape valid (${records_count} records,
${total_samples} samples, so
# --- Phase 5: stop session
------------------------------------------------------------
log "=== Phase 5: stop session ==="
-curl -fsS -XPOST "${OAP_BASE}/dsl-debugging/session/${SESSION_ID}/stop" \
+admin dsl-debug session stop "${SESSION_ID}" \
| jq -e '.localStopped == true' >/dev/null \
|| fail "localStopped != true"
diff --git a/test/e2e-v2/cases/runtime-rule/cluster/cluster-flow.sh
b/test/e2e-v2/cases/runtime-rule/cluster/cluster-flow.sh
index bf2e7cf0e9..ac371559cb 100755
--- a/test/e2e-v2/cases/runtime-rule/cluster/cluster-flow.sh
+++ b/test/e2e-v2/cases/runtime-rule/cluster/cluster-flow.sh
@@ -49,9 +49,15 @@ CONVERGE_TIMEOUT_S="${CONVERGE_TIMEOUT_S:-90}"
[ -f "${SEED_NEW}" ] || fail "seed-rule.yaml missing at ${SEED_NEW}"
+# All runtime-rule REST calls go through swctl's `admin` command tree instead
of
+# raw curl. This flow drives two OAP nodes, so the admin host (`--admin-url`)
is
+# passed per call as the first argument. `--display json` keeps the body shape
+# identical to the old curl output, so the jq assertions are unchanged.
+admin() { local base="$1"; shift; swctl --display json --admin-url="${base}"
admin "$@"; }
+
list_row() {
local base="$1"
- curl -fsS "${base}/runtime/rule/list" 2>/dev/null \
+ admin "${base}" runtime-rule list 2>/dev/null \
| jq -c '.rules[]
| select(.catalog == "'"${CATALOG}"'" and .name ==
"'"${NAME}"'")
| select(.status != "n/a")' \
@@ -113,12 +119,9 @@ await_absent() {
apply_on() {
local base="$1" body="$2" extra="${3:-}"
- local query="catalog=${CATALOG}&name=${NAME}"
- if [ -n "${extra}" ]; then
- query="${query}&${extra}"
- fi
- local resp; resp="$(curl -fsS -XPOST -H 'Content-Type: text/plain' \
- --data-binary "@${body}" "${base}/runtime/rule/addOrUpdate?${query}")"
\
+ local -a flags=(--catalog "${CATALOG}" --name "${NAME}" -f "${body}")
+ [[ "${extra}" == *allowStorageChange=true* ]] &&
flags+=(--allow-storage-change)
+ local resp; resp="$(admin "${base}" runtime-rule add "${flags[@]}")" \
|| fail "addOrUpdate against ${base} failed"
echo "${resp}"
}
@@ -126,13 +129,13 @@ apply_on() {
# --- Wait for both OAPs to come up
-------------------------------------------------
log "waiting for OAP-1 (${OAP1_BASE})"
deadline=$(( $(date +%s) + 120 ))
-until curl -fsS "${OAP1_BASE}/runtime/rule/list" >/dev/null 2>&1; do
+until admin "${OAP1_BASE}" runtime-rule list >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP-1 not ready after
120s"; fi
sleep 2
done
log "waiting for OAP-2 (${OAP2_BASE})"
deadline=$(( $(date +%s) + 120 ))
-until curl -fsS "${OAP2_BASE}/runtime/rule/list" >/dev/null 2>&1; do
+until admin "${OAP2_BASE}" runtime-rule list >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP-2 not ready after
120s"; fi
sleep 2
done
@@ -159,7 +162,7 @@ log "OAP-2 converged to ${hash_struct:0:8}…"
# --- Phase 3: inactivate on OAP-1, observe INACTIVE on OAP-2
-----------------------
log "=== Phase 3: /inactivate on OAP-1 ==="
-curl -fsS -XPOST
"${OAP1_BASE}/runtime/rule/inactivate?catalog=${CATALOG}&name=${NAME}"
>/dev/null \
+admin "${OAP1_BASE}" runtime-rule inactivate --catalog "${CATALOG}" --name
"${NAME}" >/dev/null \
|| fail "inactivate against OAP-1 failed"
await_status "${OAP1_BASE}" "INACTIVE"
log "OAP-1 → INACTIVE"
@@ -168,7 +171,7 @@ log "OAP-2 converged to INACTIVE"
# --- Phase 4: delete on OAP-1, observe row gone on OAP-2
---------------------------
log "=== Phase 4: /delete on OAP-1 ==="
-curl -fsS -XPOST
"${OAP1_BASE}/runtime/rule/delete?catalog=${CATALOG}&name=${NAME}" >/dev/null \
+admin "${OAP1_BASE}" runtime-rule delete --catalog "${CATALOG}" --name
"${NAME}" >/dev/null \
|| fail "delete against OAP-1 failed"
await_absent "${OAP1_BASE}"
log "OAP-1 → row gone"
diff --git a/test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml
b/test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml
index 477a8a52b3..d6b82f0c8d 100644
--- a/test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml
+++ b/test/e2e-v2/cases/runtime-rule/cluster/e2e.yaml
@@ -31,6 +31,8 @@ setup:
https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-linux-amd64
chmod +x /tmp/skywalking-infra-e2e/bin/jq
fi
+ - name: install swctl
+ command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl
- name: drive cluster convergence flow
command: |
set -euo pipefail
@@ -42,7 +44,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/runtime/rule/list >/dev/null &&
echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
runtime-rule list >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/runtime-rule/lal/e2e.yaml
b/test/e2e-v2/cases/runtime-rule/lal/e2e.yaml
index 3cc008a429..b02b7c2aa0 100644
--- a/test/e2e-v2/cases/runtime-rule/lal/e2e.yaml
+++ b/test/e2e-v2/cases/runtime-rule/lal/e2e.yaml
@@ -52,7 +52,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/runtime/rule/list >/dev/null &&
echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
runtime-rule list >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/runtime-rule/lal/lal-flow.sh
b/test/e2e-v2/cases/runtime-rule/lal/lal-flow.sh
index 5c3a905869..aa57fd58bd 100755
--- a/test/e2e-v2/cases/runtime-rule/lal/lal-flow.sh
+++ b/test/e2e-v2/cases/runtime-rule/lal/lal-flow.sh
@@ -58,9 +58,14 @@ SETTLE_SECONDS="${SETTLE_SECONDS:-360}"
[ -f "${SEED_V2}" ] || fail "seed v2 missing at ${SEED_V2}"
[ -f "${SEED_MAL}" ] || fail "seed mal missing at ${SEED_MAL}"
+# All runtime-rule REST calls go through swctl's `admin` command tree instead
of
+# raw curl. `--display json` keeps the response body shape identical to the old
+# curl output, so the jq assertions below are unchanged.
+admin() { swctl --display json --admin-url="${OAP_BASE}" admin "$@"; }
+
list_row() {
local catalog="$1" name="$2"
- curl -fsS "${OAP_BASE}/runtime/rule/list" 2>/dev/null \
+ admin runtime-rule list 2>/dev/null \
| jq -c '.rules[]
| select(.catalog == "'"${catalog}"'" and .name ==
"'"${name}"'")
| select(.status != "n/a")' \
@@ -73,22 +78,20 @@ list_field() {
apply_rule() {
local catalog="$1" name="$2" body="$3"
- curl -fsS -XPOST -H 'Content-Type: text/plain' \
- --data-binary "@${body}" \
- "${OAP_BASE}/runtime/rule/addOrUpdate?catalog=${catalog}&name=${name}"
>/dev/null \
+ admin runtime-rule add --catalog "${catalog}" --name "${name}" -f
"${body}" >/dev/null \
|| fail "addOrUpdate ${catalog}/${name} from ${body} failed"
}
# Retries 503 cluster_not_ready for up to 60s — the reconciler's peer-refresh
# window briefly returns 503 right after a structural reshape (e.g. LAL
-# delete that retires its dispatcher). Mirrors the MAL flow's pattern.
-retry_post() {
- local url="$1"
+# delete that retires its dispatcher). Mirrors the MAL flow's pattern. Pass the
+# runtime-rule subcommand and its flags.
+retry_admin() {
local deadline=$(( $(date +%s) + 60 ))
local out
while (( $(date +%s) < deadline )); do
- out="$(curl -fsS -XPOST "${url}" 2>&1)" && return 0
- if [[ "${out}" == *503* ]]; then
+ out="$(admin "$@" 2>&1)" && return 0
+ if echo "${out}" | grep -q "HTTP 503"; then
sleep 2
continue
fi
@@ -101,13 +104,13 @@ retry_post() {
inactivate_rule() {
local catalog="$1" name="$2"
- retry_post
"${OAP_BASE}/runtime/rule/inactivate?catalog=${catalog}&name=${name}"
>/dev/null \
+ retry_admin runtime-rule inactivate --catalog "${catalog}" --name
"${name}" >/dev/null \
|| fail "inactivate ${catalog}/${name} failed"
}
delete_rule() {
local catalog="$1" name="$2"
- retry_post
"${OAP_BASE}/runtime/rule/delete?catalog=${catalog}&name=${name}" >/dev/null \
+ retry_admin runtime-rule delete --catalog "${catalog}" --name "${name}"
>/dev/null \
|| fail "delete ${catalog}/${name} failed"
}
@@ -143,7 +146,7 @@ await_metric_for_step() {
log "waiting for OAP runtime-rule port"
deadline=$(( $(date +%s) + 90 ))
-until curl -fsS "${OAP_BASE}/runtime/rule/list" >/dev/null 2>&1; do
+until admin runtime-rule list >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "${deadline}" ]; then fail "OAP not ready after
90s"; fi
sleep 2
done
diff --git a/test/e2e-v2/cases/runtime-rule/mal-storage/banyandb/e2e.yaml
b/test/e2e-v2/cases/runtime-rule/mal-storage/banyandb/e2e.yaml
index fb8033a540..9d09a5fc14 100644
--- a/test/e2e-v2/cases/runtime-rule/mal-storage/banyandb/e2e.yaml
+++ b/test/e2e-v2/cases/runtime-rule/mal-storage/banyandb/e2e.yaml
@@ -65,7 +65,7 @@ verify:
# minimal so the harness reports the script's pass/fail directly. A
trailing
# /list smoke check confirms the receiver port is still serving after the
# destructive phase (catches regressions where /delete crashes the
handler).
- - query: curl -fsS http://127.0.0.1:17128/runtime/rule/list >/dev/null &&
echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
runtime-rule list >/dev/null && echo ok
expected: expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/runtime-rule/mal-storage/elasticsearch/e2e.yaml
b/test/e2e-v2/cases/runtime-rule/mal-storage/elasticsearch/e2e.yaml
index 260773f1b2..d449ed83aa 100644
--- a/test/e2e-v2/cases/runtime-rule/mal-storage/elasticsearch/e2e.yaml
+++ b/test/e2e-v2/cases/runtime-rule/mal-storage/elasticsearch/e2e.yaml
@@ -56,7 +56,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/runtime/rule/list >/dev/null &&
echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
runtime-rule list >/dev/null && echo ok
expected: ../expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/runtime-rule/mal-storage/postgresql/e2e.yaml
b/test/e2e-v2/cases/runtime-rule/mal-storage/postgresql/e2e.yaml
index f15d0afd19..6562aae4ec 100644
--- a/test/e2e-v2/cases/runtime-rule/mal-storage/postgresql/e2e.yaml
+++ b/test/e2e-v2/cases/runtime-rule/mal-storage/postgresql/e2e.yaml
@@ -57,7 +57,7 @@ verify:
count: 1
interval: 1s
cases:
- - query: curl -fsS http://127.0.0.1:17128/runtime/rule/list >/dev/null &&
echo ok
+ - query: swctl --display json --admin-url=http://127.0.0.1:17128 admin
runtime-rule list >/dev/null && echo ok
expected: ../expected/ok.txt
cleanup:
diff --git a/test/e2e-v2/cases/runtime-rule/mal-storage/runtime-rule-flow.sh
b/test/e2e-v2/cases/runtime-rule/mal-storage/runtime-rule-flow.sh
index 97831551aa..d9832ec10a 100755
--- a/test/e2e-v2/cases/runtime-rule/mal-storage/runtime-rule-flow.sh
+++ b/test/e2e-v2/cases/runtime-rule/mal-storage/runtime-rule-flow.sh
@@ -64,6 +64,14 @@ GQL_BASE="http://${OAP_HOST}:${OAP_GQL_PORT}"
log() { echo "[runtime-rule-flow] $*" >&2; }
fail() { echo "[runtime-rule-flow] FAIL: $*" >&2; exit 1; }
+# Every runtime-rule REST call goes through swctl's `admin` command tree
instead
+# of raw curl. `--display json` keeps the response body byte-shape identical to
+# the old curl output (the runtime-rule endpoints are passed through verbatim),
+# so the jq assertions below are unchanged. On a non-2xx the CLI exits non-zero
+# and renders the typed error envelope — `admin API <url>: HTTP <code>
+# (<applyStatus>): <message>` — which the negative-path helpers grep for.
+admin() { swctl --display json --admin-url="${REST_BASE}" admin "$@"; }
+
# Resolve the otlp-emitter container by name fragment so we don't need to know
# the compose project name. Cached on first lookup.
EMITTER_CONTAINER=""
@@ -89,101 +97,103 @@ step_set() {
log " step=${value}"
}
-# Retry a 2xx-or-fail curl for up to RETRY_BUDGET_S seconds. Exists because the
-# cluster routing layer transiently returns 503 cluster_not_ready when its peer
+# Retry a runtime-rule admin call for up to RETRY_BUDGET_S seconds. Exists
because
+# the cluster routing layer transiently returns 503 cluster_not_ready when its
peer
# refresh is in flight; happens reliably right after a STRUCTURAL apply (the
# reconciler's cache may be paused). Operator retries after a few seconds work
-# in practice, so the e2e applies the same pattern automatically.
+# in practice, so the e2e applies the same pattern automatically. Pass the
+# runtime-rule subcommand and its flags, e.g.
+# retry_admin runtime-rule inactivate --catalog "${CATALOG}" --name "${NAME}"
RETRY_BUDGET_S="${RETRY_BUDGET_S:-60}"
-retry_curl_post() {
- local url="$1"
- local body_arg="${2:-}" # e.g. --data-binary @file ; empty for empty-body
POST
+retry_admin() {
local deadline=$(( $(date +%s) + RETRY_BUDGET_S ))
- local out
+ local out rc
while (( $(date +%s) < deadline )); do
- if [[ -n "${body_arg}" ]]; then
- # shellcheck disable=SC2086
- out="$(curl -fsS -XPOST ${body_arg} -H "Content-Type: text/plain"
"${url}" 2>&1)" && {
- echo "${out}"; return 0;
- }
- else
- out="$(curl -fsS -XPOST "${url}" 2>&1)" && { echo "${out}"; return 0; }
- fi
- if [[ "${out}" == *503* ]]; then
- log " transient 503 on ${url} — retrying"
+ out="$(admin "$@" 2>&1)" && { echo "${out}"; return 0; }
+ rc=$?
+ if echo "${out}" | grep -q "HTTP 503"; then
+ log " transient 503 on 'admin $*' — retrying"
sleep 2
continue
fi
echo "${out}"
- return 1
+ return "${rc}"
done
echo "${out}"
return 1
}
-# POST a rule file to /addOrUpdate. Echoes the JSON response. Asserts 200.
+# Apply a rule file via addOrUpdate. Echoes the JSON response. Asserts 2xx.
+# extra="allowStorageChange=true" maps to the --allow-storage-change flag.
post_rule() {
local file="$1"
- local extra_qs="${2:-}"
+ local extra="${2:-}"
local rule_name="${3:-${NAME}}"
- local
url="${REST_BASE}/runtime/rule/addOrUpdate?catalog=${CATALOG}&name=${rule_name}${extra_qs:+&${extra_qs}}"
- log "POST ${url} (body=${file})"
+ local -a flags=(--catalog "${CATALOG}" --name "${rule_name}" -f "${file}")
+ [[ "${extra}" == *allowStorageChange=true* ]] &&
flags+=(--allow-storage-change)
+ log "runtime-rule add ${CATALOG}/${rule_name} (body=${file})"
local resp
- resp="$(curl -fsS -XPOST --data-binary "@${file}" -H "Content-Type:
text/plain" "${url}")" \
+ resp="$(admin runtime-rule add "${flags[@]}")" \
|| fail "addOrUpdate of ${file} returned non-2xx"
log " → ${resp}"
echo "${resp}"
}
-# POST a rule that's expected to be REJECTED. Captures the HTTP status and the
-# response body via curl's separate -w / -o, asserts the status matches, and
-# echoes the body so callers can grep for a specific failure code/string.
+# Apply a rule that's expected to be REJECTED. swctl exits non-zero on a
non-2xx
+# and renders the typed error envelope ("... HTTP <code> (<applyStatus>):
<msg>")
+# to stdout; assert the HTTP code is present and echo the message so callers
can
+# grep for a specific failure code / applyStatus / string.
post_rule_expect_status() {
local file="$1"
local expected_status="$2"
- local extra_qs="${3:-}"
+ local extra="${3:-}"
local rule_name="${4:-${NAME}}"
- local
url="${REST_BASE}/runtime/rule/addOrUpdate?catalog=${CATALOG}&name=${rule_name}${extra_qs:+&${extra_qs}}"
- log "POST ${url} (expect HTTP ${expected_status}, body=${file})"
- local body_file http_status
- body_file="$(mktemp)"
- http_status="$(curl -sS -o "${body_file}" -w '%{http_code}' \
- -XPOST --data-binary "@${file}" -H "Content-Type: text/plain" "${url}")"
- local body
- body="$(cat "${body_file}")"
- rm -f "${body_file}"
- log " ← HTTP ${http_status} body=${body}"
- [[ "${http_status}" == "${expected_status}" ]] \
- || fail "expected HTTP ${expected_status}, got ${http_status} (body:
${body})"
- echo "${body}"
+ local -a flags=(--catalog "${CATALOG}" --name "${rule_name}" -f "${file}")
+ [[ "${extra}" == *allowStorageChange=true* ]] &&
flags+=(--allow-storage-change)
+ log "runtime-rule add ${CATALOG}/${rule_name} (expect HTTP
${expected_status}, body=${file})"
+ local out rc
+ out="$(admin runtime-rule add "${flags[@]}" 2>&1)" && rc=0 || rc=$?
+ log " ← rc=${rc} ${out}"
+ [[ "${rc}" -ne 0 ]] \
+ || fail "expected rejection (HTTP ${expected_status}) but add succeeded:
${out}"
+ echo "${out}" | grep -q "HTTP ${expected_status}" \
+ || fail "expected HTTP ${expected_status}, got: ${out}"
+ echo "${out}"
}
-# POST a non-/addOrUpdate endpoint that's expected to be REJECTED. Same
-# semantics as post_rule_expect_status but takes an explicit URL.
-post_url_expect_status() {
- local url="$1"
+# Delete a rule that's expected to be REJECTED (e.g. /delete on an ACTIVE row →
+# 409 requires_inactivate_first). Same envelope-grep semantics as
+# post_rule_expect_status.
+delete_expect_status() {
+ local rule_name="$1"
local expected_status="$2"
- log "POST ${url} (expect HTTP ${expected_status})"
- local body_file http_status
- body_file="$(mktemp)"
- http_status="$(curl -sS -o "${body_file}" -w '%{http_code}' -XPOST "${url}")"
- local body
- body="$(cat "${body_file}")"
- rm -f "${body_file}"
- log " ← HTTP ${http_status} body=${body}"
- [[ "${http_status}" == "${expected_status}" ]] \
- || fail "expected HTTP ${expected_status}, got ${http_status} (body:
${body})"
- echo "${body}"
+ log "runtime-rule delete ${CATALOG}/${rule_name} (expect HTTP
${expected_status})"
+ local out rc
+ out="$(admin runtime-rule delete --catalog "${CATALOG}" --name
"${rule_name}" 2>&1)" && rc=0 || rc=$?
+ log " ← rc=${rc} ${out}"
+ [[ "${rc}" -ne 0 ]] \
+ || fail "expected delete rejection (HTTP ${expected_status}) but it
succeeded: ${out}"
+ echo "${out}" | grep -q "HTTP ${expected_status}" \
+ || fail "expected HTTP ${expected_status}, got: ${out}"
+ echo "${out}"
}
-# Assert the JSON response carries the expected applyStatus.
+# Assert the expected applyStatus. On the happy path the argument is the JSON
+# ApplyResult and the status comes from .applyStatus. On a rejection the
argument
+# is swctl's error line, where the CLI's typed envelope renders the
applyStatus in
+# parentheses, e.g. "... HTTP 400 (layer_ordinal_out_of_range): <msg>".
assert_apply_status() {
local expected="$1"
- local actual_json="$2"
- local actual
- actual="$(echo "${actual_json}" | jq -r '.applyStatus // empty')"
- [[ "${actual}" == "${expected}" ]] \
- || fail "expected applyStatus=${expected}, got '${actual}' (full:
${actual_json})"
+ local actual="$2"
+ local parsed
+ parsed="$(echo "${actual}" | jq -r '.applyStatus // empty' 2>/dev/null ||
true)"
+ if [[ -n "${parsed}" ]]; then
+ [[ "${parsed}" == "${expected}" ]] \
+ || fail "expected applyStatus=${expected}, got '${parsed}' (full:
${actual})"
+ return 0
+ fi
+ echo "${actual}" | grep -q "(${expected})" \
+ || fail "expected applyStatus=${expected}, not found in: ${actual}"
}
# GET /runtime/rule/list and ensure the row matches the expected status.
Returns
@@ -191,10 +201,10 @@ assert_apply_status() {
list_row() {
local expected_status="$1"
local rule_name="${2:-${NAME}}"
- log "GET /runtime/rule/list → looking for ${CATALOG}/${rule_name}
status=${expected_status}"
+ log "runtime-rule list → looking for ${CATALOG}/${rule_name}
status=${expected_status}"
local lines
- lines="$(curl -fsS "${REST_BASE}/runtime/rule/list")" \
- || fail "GET /runtime/rule/list failed"
+ lines="$(admin runtime-rule list)" \
+ || fail "runtime-rule list failed"
local match
match="$(echo "${lines}" | jq -c ".rules[] | select(.catalog==\"${CATALOG}\"
and .name==\"${rule_name}\")" 2>/dev/null || true)"
[[ -n "${match}" ]] \
@@ -209,10 +219,10 @@ list_row() {
# Assert that /list does NOT have a row for the given (catalog, name).
list_no_row() {
local rule_name="${1:-${NAME}}"
- log "GET /runtime/rule/list → expect NO row for ${CATALOG}/${rule_name}"
+ log "runtime-rule list → expect NO row for ${CATALOG}/${rule_name}"
local lines match
- lines="$(curl -fsS "${REST_BASE}/runtime/rule/list")" \
- || fail "GET /runtime/rule/list failed"
+ lines="$(admin runtime-rule list)" \
+ || fail "runtime-rule list failed"
match="$(echo "${lines}" | jq -c ".rules[] | select(.catalog==\"${CATALOG}\"
and .name==\"${rule_name}\")" 2>/dev/null || true)"
if [[ -n "${match}" ]]; then
local status
@@ -404,8 +414,8 @@ assert_dump_contains() {
shift
local tar_file
tar_file="$(mktemp)"
- curl -fsS "${REST_BASE}/runtime/rule/dump" -o "${tar_file}" \
- || fail "GET /runtime/rule/dump failed (${label})"
+ admin runtime-rule dump -o "${tar_file}" >/dev/null \
+ || fail "runtime-rule dump failed (${label})"
local entries
entries="$(tar -tzf "${tar_file}" 2>&1)" \
|| { rm -f "${tar_file}"; fail "${label}: dump body is not a valid tar.gz:
${entries}"; }
@@ -422,7 +432,7 @@ assert_dump_contains() {
log "waiting for OAP runtime-rule port ${OAP_REST_PORT}"
for _ in $(seq 1 60); do
- curl -fsS "${REST_BASE}/runtime/rule/list" >/dev/null 2>&1 && break
+ admin runtime-rule list >/dev/null 2>&1 && break
sleep 2
done
@@ -495,7 +505,7 @@ assert_metric_step_advanced "e2e_rr_requests" "structural"
"${struct_baseline}"
log "=== Phase 5c: ILLEGAL /delete on ACTIVE row ==="
struct_baseline="$(latest_bucket_id_for_step "e2e_rr_requests" "structural")"
-post_url_expect_status
"${REST_BASE}/runtime/rule/delete?catalog=${CATALOG}&name=${NAME}" "409"
>/dev/null
+delete_expect_status "${NAME}" "409" >/dev/null
[[ "$(list_row ACTIVE | jq -r '.contentHash')" == "${hash_structural}" ]] \
|| fail "5c: row state changed after /delete-on-ACTIVE rejection"
assert_metric_step_advanced "e2e_rr_requests" "structural"
"${struct_baseline}" 180
@@ -540,7 +550,9 @@ resp="$(post_rule_expect_status \
"${SEED_RULES_DIR}/illegal-layer-name-conflict.yaml" "400" ""
"${SIBLING_NAME}")"
assert_apply_status "layer_name_conflict" "${resp}"
# Message must name the conflicting source so operators see what to align with.
-echo "${resp}" | jq -e '.message | test("built-in")' >/dev/null \
+# resp is swctl's plain-text error envelope ("... (layer_name_conflict):
<msg>"),
+# not JSON, so grep the message directly rather than parsing it.
+echo "${resp}" | grep -q "built-in" \
|| fail "5g: response message did not label source as built-in: ${resp}"
list_no_row "${SIBLING_NAME}"
[[ "$(list_row ACTIVE | jq -r '.contentHash')" == "${hash_structural}" ]] \
@@ -576,13 +588,13 @@ oap_container="$(docker ps --filter
"ancestor=skywalking/oap:latest" \
docker restart "${oap_container}" >/dev/null
# Wait for the REST port to come back. Cap at 180s so a true hang surfaces.
for i in $(seq 1 90); do
- if curl -fsS "${REST_BASE}/runtime/rule/list" >/dev/null 2>&1; then
+ if admin runtime-rule list >/dev/null 2>&1; then
log " OAP back up after ${i}*2s"
break
fi
sleep 2
done
-curl -fsS "${REST_BASE}/runtime/rule/list" >/dev/null \
+admin runtime-rule list >/dev/null \
|| fail "5h: OAP did not come back online after restart"
# Critical assertion: the runtime layer must still be visible AND retain its
@@ -599,9 +611,9 @@ list_row "ACTIVE" "${SIBLING_NAME}" >/dev/null
log " post-restart: layer + rule survived"
# Now prove the layer is still removable through the dynamic channel.
-retry_curl_post
"${REST_BASE}/runtime/rule/inactivate?catalog=${CATALOG}&name=${SIBLING_NAME}"
>/dev/null \
+retry_admin runtime-rule inactivate --catalog "${CATALOG}" --name
"${SIBLING_NAME}" >/dev/null \
|| fail "5h: sibling inactivate failed"
-retry_curl_post
"${REST_BASE}/runtime/rule/delete?catalog=${CATALOG}&name=${SIBLING_NAME}"
>/dev/null \
+retry_admin runtime-rule delete --catalog "${CATALOG}" --name
"${SIBLING_NAME}" >/dev/null \
|| fail "5h: sibling delete failed"
list_no_row "${SIBLING_NAME}"
sleep 2
@@ -619,14 +631,12 @@ assert_metric_step_advanced "e2e_rr_requests"
"structural" "${struct_baseline}"
# POST a new shape under the same (catalog, name).
log "=== Phase 6: SHAPE-BREAK ==="
step_set "shape_break_old"
-log " /inactivate to release the old shape"
-inactivate_url="${REST_BASE}/runtime/rule/inactivate?catalog=${CATALOG}&name=${NAME}"
-retry_curl_post "${inactivate_url}" >/dev/null \
+log " inactivate to release the old shape"
+retry_admin runtime-rule inactivate --catalog "${CATALOG}" --name "${NAME}"
>/dev/null \
|| fail "shape-break: inactivate failed"
list_row "INACTIVE" >/dev/null
-log " /delete to drop the old measure"
-delete_url="${REST_BASE}/runtime/rule/delete?catalog=${CATALOG}&name=${NAME}"
-retry_curl_post "${delete_url}" >/dev/null \
+log " delete to drop the old measure"
+retry_admin runtime-rule delete --catalog "${CATALOG}" --name "${NAME}"
>/dev/null \
|| fail "shape-break: delete failed"
list_no_row
@@ -647,7 +657,7 @@ await_metric_for_step "e2e_rr_requests" "shape_break_new"
# window where the rule is still active aggregates a few `step=inactivate`
# samples and the soft-pause assertion below fails for the wrong reason.
log "=== Phase 7: INACTIVATE (soft-pause) ==="
-retry_curl_post "${inactivate_url}" >/dev/null \
+retry_admin runtime-rule inactivate --catalog "${CATALOG}" --name "${NAME}"
>/dev/null \
|| fail "phase-7: inactivate failed"
list_row "INACTIVE" >/dev/null
step_set "inactivate"
@@ -673,10 +683,10 @@ await_metric_for_step "e2e_rr_requests" "activate"
# Phase 9 — DELETE (destructive).
log "=== Phase 9: DELETE ==="
step_set "delete_attempt"
-retry_curl_post "${inactivate_url}" >/dev/null \
+retry_admin runtime-rule inactivate --catalog "${CATALOG}" --name "${NAME}"
>/dev/null \
|| fail "phase-9: inactivate-before-delete failed"
list_row "INACTIVE" >/dev/null
-retry_curl_post "${delete_url}" >/dev/null \
+retry_admin runtime-rule delete --catalog "${CATALOG}" --name "${NAME}"
>/dev/null \
|| fail "phase-9: delete failed"
list_no_row
log " ✓ row gone + backend probe agrees"
diff --git a/test/e2e-v2/cases/storage/banyandb/e2e.yaml
b/test/e2e-v2/cases/storage/banyandb/e2e.yaml
index 9b33becacf..c59388a5d5 100644
--- a/test/e2e-v2/cases/storage/banyandb/e2e.yaml
+++ b/test/e2e-v2/cases/storage/banyandb/e2e.yaml
@@ -64,7 +64,7 @@ verify:
- query: swctl --display yaml
--base-url=http://${oap_host}:${oap_12800}/graphql tv2 ls --tags
http.method=POST,http.status_code=201
expected: ../expected/empty-traces-v2-list.yml
- query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../expected/ttl-config-banyandb.yml
cleanup:
on: always
diff --git a/test/e2e-v2/cases/storage/es/e2e.yaml
b/test/e2e-v2/cases/storage/es/e2e.yaml
index d8bb380b29..1e07021a0b 100644
--- a/test/e2e-v2/cases/storage/es/e2e.yaml
+++ b/test/e2e-v2/cases/storage/es/e2e.yaml
@@ -72,5 +72,5 @@ verify:
)
expected: ../expected/trace-users-detail.yml
- query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../expected/ttl-config.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/es/es-sharding/e2e.yaml
b/test/e2e-v2/cases/storage/es/es-sharding/e2e.yaml
index f81c6ea828..cad4c17943 100644
--- a/test/e2e-v2/cases/storage/es/es-sharding/e2e.yaml
+++ b/test/e2e-v2/cases/storage/es/es-sharding/e2e.yaml
@@ -72,5 +72,5 @@ verify:
)
expected: ../../expected/trace-users-detail.yml
- query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../../expected/ttl-config.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/expected/config-dump.yml
b/test/e2e-v2/cases/storage/expected/config-dump.yml
index 24cd41e5e5..a95bda9e18 100644
--- a/test/e2e-v2/cases/storage/expected/config-dump.yml
+++ b/test/e2e-v2/cases/storage/expected/config-dump.yml
@@ -13,216 +13,218 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-admin-server.default.acceptQueueSize=0
-admin-server.default.contextPath=/
-admin-server.default.gRPCHost=0.0.0.0
-admin-server.default.gRPCMaxConcurrentCallsPerConnection=0
-admin-server.default.gRPCMaxMessageSize=52428800
-admin-server.default.gRPCPort=17129
-admin-server.default.gRPCSslCertChainPath=
-admin-server.default.gRPCSslEnabled=false
-admin-server.default.gRPCSslKeyPath=
-admin-server.default.gRPCSslTrustedCAsPath=
-admin-server.default.gRPCThreadPoolSize=0
-admin-server.default.host=0.0.0.0
-admin-server.default.httpMaxRequestHeaderSize=8192
-admin-server.default.idleTimeOut=30000
-admin-server.default.internalCommunicationTimeout=5000
-admin-server.default.port=17128
-admin-server.provider=default
-agent-analyzer.default.forceSampleErrorSegment=true
-agent-analyzer.default.meterAnalyzerActiveFiles=datasource,threadpool,satellite,go-runtime,python-runtime,continuous-profiling,java-agent,go-agent,ruby-runtime
-agent-analyzer.default.noUpstreamRealAddressAgents=6000,9000
-agent-analyzer.default.segmentStatusAnalysisStrategy=FROM_SPAN_STATUS
-agent-analyzer.default.slowCacheReadThreshold=default:20,redis:10
-agent-analyzer.default.slowCacheWriteThreshold=default:20,redis:10
-agent-analyzer.default.slowDBAccessThreshold=default:200,mongodb:100
-agent-analyzer.default.traceSamplingPolicySettingsFile=trace-sampling-policy-settings.yml
-agent-analyzer.provider=default
-ai-pipeline.default.baselineServerAddr=
-ai-pipeline.default.baselineServerPort=18080
-ai-pipeline.default.uriRecognitionServerAddr=
-ai-pipeline.default.uriRecognitionServerPort=17128
-ai-pipeline.provider=default
-alarm.provider=default
-aws-firehose.default.acceptQueueSize=0
-aws-firehose.default.contextPath=/
-aws-firehose.default.enableTLS=false
-aws-firehose.default.firehoseAccessKey=******
-aws-firehose.default.host=0.0.0.0
-aws-firehose.default.idleTimeOut=30000
-aws-firehose.default.maxRequestHeaderSize=8192
-aws-firehose.default.port=12801
-aws-firehose.default.tlsCertChainPath=
-aws-firehose.default.tlsKeyPath=
-aws-firehose.provider=default
-cluster.provider=standalone
-configuration-discovery.default.disableMessageDigest=false
-configuration-discovery.provider=default
-configuration.provider=none
-core.default.activeExtraModelColumns=false
-core.default.autocompleteTagKeysQueryMaxSize=100
-core.default.autocompleteTagValuesQueryMaxSize=100
-core.default.dataKeeperExecutePeriod=5
-core.default.downsampling=[Hour, Day]
-core.default.enableDataKeeperExecutor=true
-core.default.enableEndpointNameGroupingByOpenapi=true
-core.default.enableHierarchy=true
-core.default.endpointNameMaxLength=150
-core.default.gRPCHost=0.0.0.0
-core.default.gRPCPort=11800
-core.default.gRPCSslCertChainPath=
-core.default.gRPCSslEnabled=false
-core.default.gRPCSslKeyPath=
-core.default.gRPCSslTrustedCAPath=
-core.default.gRPCThreadPoolSize=-1
-core.default.httpMaxRequestHeaderSize=8192
-core.default.instanceNameMaxLength=70
-core.default.l1FlushPeriod=500
-core.default.maxConcurrentCallsPerConnection=0
-core.default.maxDirectMemoryUsage=-1
-core.default.maxHeapMemoryUsagePercent=96
-core.default.maxHttpUrisNumberPerService=3000
-core.default.maxMessageSize=52428800
-core.default.metricsDataTTL=7
-core.default.persistentPeriod=25
-core.default.prepareThreads=2
-core.default.recordDataTTL=3
-core.default.restAcceptQueueSize=0
-core.default.restContextPath=/
-core.default.restHost=0.0.0.0
-core.default.restIdleTimeOut=30000
-core.default.restPort=12800
-core.default.role=Mixed
-core.default.searchableAlarmTags=level
-core.default.searchableLogsTags=level,http.status_code,ai_route_type
-core.default.searchableTracesTags=http.method,http.status_code,rpc.status_code,db.type,db.instance,mq.queue,mq.topic,mq.broker
-core.default.serviceCacheRefreshInterval=10
-core.default.serviceNameMaxLength=70
-core.default.storageSessionTimeout=70000
-core.default.syncPeriodHttpUriRecognitionPattern=10
-core.default.topNReportPeriod=10
-core.default.trainingPeriodHttpUriRecognitionPattern=60
-core.provider=default
-dsl-debugging.default.injectionEnabled=true
-dsl-debugging.provider=default
-envoy-metric.default.acceptMetricsService=true
-envoy-metric.default.alsHTTPAnalysis=
-envoy-metric.default.alsTCPAnalysis=
-envoy-metric.default.enabledEnvoyMetricsRules=envoy,envoy-svc-relation
-envoy-metric.default.gRPCHost=0.0.0.0
-envoy-metric.default.gRPCPort=0
-envoy-metric.default.gRPCSslCertChainPath=
-envoy-metric.default.gRPCSslEnabled=false
-envoy-metric.default.gRPCSslKeyPath=
-envoy-metric.default.gRPCSslTrustedCAsPath=
-envoy-metric.default.gRPCThreadPoolSize=0
-envoy-metric.default.istioServiceEntryIgnoredNamespaces=
-envoy-metric.default.istioServiceNameRule=${serviceEntry.metadata.name}.${serviceEntry.metadata.namespace}
-envoy-metric.default.k8sServiceNameRule=${pod.metadata.labels.(service.istio.io/canonical-name)}.${pod.metadata.namespace}
-envoy-metric.default.maxConcurrentCallsPerConnection=0
-envoy-metric.default.maxMessageSize=0
-envoy-metric.provider=default
-event-analyzer.provider=default
-gen-ai-analyzer.provider=default
-health-checker.default.checkIntervalSeconds=30
-health-checker.provider=default
-inspect.provider=default
-log-analyzer.default.lalFiles=envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default
-log-analyzer.default.malFiles=nginx,miniprogram-wechat,miniprogram-alipay
-log-analyzer.provider=default
-logql.default.restAcceptQueueSize=0
-logql.default.restContextPath=/
-logql.default.restHost=0.0.0.0
-logql.default.restIdleTimeOut=30000
-logql.default.restPort=3100
-logql.provider=default
-promql.default.buildInfoBranch=
-promql.default.buildInfoBuildDate=
-promql.default.buildInfoBuildUser=******
-promql.default.buildInfoGoVersion=
-promql.default.buildInfoRevision=
-promql.default.buildInfoVersion=2.45.0
-promql.default.restAcceptQueueSize=0
-promql.default.restContextPath=/
-promql.default.restHost=0.0.0.0
-promql.default.restIdleTimeOut=30000
-promql.default.restPort=9090
-promql.provider=default
-query.graphql.enableLogTestTool=false
-query.graphql.enableOnDemandPodLog=false
-query.graphql.maxQueryComplexity=3000
-query.provider=graphql
-receiver-async-profiler.default.jfrMaxSize=31457280
-receiver-async-profiler.default.memoryParserEnabled=true
-receiver-async-profiler.provider=default
-receiver-browser.default.sampleRate=10000
-receiver-browser.provider=default
-receiver-clr.provider=default
-receiver-ebpf.default.continuousPolicyCacheTimeout=60
-receiver-ebpf.default.gRPCHost=0.0.0.0
-receiver-ebpf.default.gRPCPort=0
-receiver-ebpf.default.gRPCSslCertChainPath=
-receiver-ebpf.default.gRPCSslEnabled=false
-receiver-ebpf.default.gRPCSslKeyPath=
-receiver-ebpf.default.gRPCSslTrustedCAsPath=
-receiver-ebpf.default.gRPCThreadPoolSize=0
-receiver-ebpf.default.maxConcurrentCallsPerConnection=0
-receiver-ebpf.default.maxMessageSize=0
-receiver-ebpf.provider=default
-receiver-event.provider=default
-receiver-jvm.provider=default
-receiver-log.provider=default
-receiver-meter.provider=default
-receiver-otel.default.enabledHandlers=otlp-traces,otlp-metrics,otlp-logs
-receiver-otel.default.enabledOtelMetricsRules=apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/*
-receiver-otel.provider=default
-receiver-pprof.default.memoryParserEnabled=true
-receiver-pprof.default.pprofMaxSize=31457280
-receiver-pprof.provider=default
-receiver-profile.provider=default
-receiver-register.provider=default
-receiver-runtime-rule.default.refreshRulesPeriod=30
-receiver-runtime-rule.default.selfHealThresholdSeconds=60
-receiver-runtime-rule.provider=default
-receiver-sharing-server.default.authentication=******
-receiver-sharing-server.default.gRPCHost=0.0.0.0
-receiver-sharing-server.default.gRPCPort=0
-receiver-sharing-server.default.gRPCSslCertChainPath=
-receiver-sharing-server.default.gRPCSslEnabled=false
-receiver-sharing-server.default.gRPCSslKeyPath=
-receiver-sharing-server.default.gRPCSslTrustedCAsPath=
-receiver-sharing-server.default.gRPCThreadPoolSize=0
-receiver-sharing-server.default.httpMaxRequestHeaderSize=8192
-receiver-sharing-server.default.maxConcurrentCallsPerConnection=0
-receiver-sharing-server.default.maxMessageSize=52428800
-receiver-sharing-server.default.restAcceptQueueSize=0
-receiver-sharing-server.default.restContextPath=/
-receiver-sharing-server.default.restHost=0.0.0.0
-receiver-sharing-server.default.restIdleTimeOut=30000
-receiver-sharing-server.default.restPort=0
-receiver-sharing-server.provider=default
-receiver-telegraf.default.activeFiles=vm
-receiver-telegraf.provider=default
-receiver-trace.provider=default
-service-mesh.provider=default
-status.default.keywords4MaskingSecretsOfConfig=user,password,trustStorePass,keyStorePass,token,accessKey,secretKey,authentication
-status.provider=default
-storage.mysql.asyncBatchPersistentPoolSize=4
-storage.mysql.maxSizeOfBatchSql=2000
-storage.mysql.metadataQueryMaxSize=5000
-storage.mysql.properties.dataSource.cachePrepStmts=true
-storage.mysql.properties.dataSource.password=******
-storage.mysql.properties.dataSource.prepStmtCacheSize=250
-storage.mysql.properties.dataSource.prepStmtCacheSqlLimit=2048
-storage.mysql.properties.dataSource.useServerPrepStmts=true
-storage.mysql.properties.dataSource.user=******
-storage.mysql.properties.jdbcUrl=jdbc:mysql://mysql:3306/swtest?allowMultiQueries=true
-storage.provider=mysql
-telemetry.prometheus.host=0.0.0.0
-telemetry.prometheus.port=1234
-telemetry.prometheus.sslCertChainPath=
-telemetry.prometheus.sslEnabled=false
-telemetry.prometheus.sslKeyPath=
-telemetry.provider=prometheus
-ui-management.provider=default
+{
+ "admin-server.default.acceptQueueSize": "0",
+ "admin-server.default.contextPath": "/",
+ "admin-server.default.gRPCHost": "0.0.0.0",
+ "admin-server.default.gRPCMaxConcurrentCallsPerConnection": "0",
+ "admin-server.default.gRPCMaxMessageSize": "52428800",
+ "admin-server.default.gRPCPort": "17129",
+ "admin-server.default.gRPCSslCertChainPath": "",
+ "admin-server.default.gRPCSslEnabled": "false",
+ "admin-server.default.gRPCSslKeyPath": "",
+ "admin-server.default.gRPCSslTrustedCAsPath": "",
+ "admin-server.default.gRPCThreadPoolSize": "0",
+ "admin-server.default.host": "0.0.0.0",
+ "admin-server.default.httpMaxRequestHeaderSize": "8192",
+ "admin-server.default.idleTimeOut": "30000",
+ "admin-server.default.internalCommunicationTimeout": "5000",
+ "admin-server.default.port": "17128",
+ "admin-server.provider": "default",
+ "agent-analyzer.default.forceSampleErrorSegment": "true",
+ "agent-analyzer.default.meterAnalyzerActiveFiles":
"datasource,threadpool,satellite,go-runtime,python-runtime,continuous-profiling,java-agent,go-agent,ruby-runtime",
+ "agent-analyzer.default.noUpstreamRealAddressAgents": "6000,9000",
+ "agent-analyzer.default.segmentStatusAnalysisStrategy": "FROM_SPAN_STATUS",
+ "agent-analyzer.default.slowCacheReadThreshold": "default:20,redis:10",
+ "agent-analyzer.default.slowCacheWriteThreshold": "default:20,redis:10",
+ "agent-analyzer.default.slowDBAccessThreshold": "default:200,mongodb:100",
+ "agent-analyzer.default.traceSamplingPolicySettingsFile":
"trace-sampling-policy-settings.yml",
+ "agent-analyzer.provider": "default",
+ "ai-pipeline.default.baselineServerAddr": "",
+ "ai-pipeline.default.baselineServerPort": "18080",
+ "ai-pipeline.default.uriRecognitionServerAddr": "",
+ "ai-pipeline.default.uriRecognitionServerPort": "17128",
+ "ai-pipeline.provider": "default",
+ "alarm.provider": "default",
+ "aws-firehose.default.acceptQueueSize": "0",
+ "aws-firehose.default.contextPath": "/",
+ "aws-firehose.default.enableTLS": "false",
+ "aws-firehose.default.firehoseAccessKey": "******",
+ "aws-firehose.default.host": "0.0.0.0",
+ "aws-firehose.default.idleTimeOut": "30000",
+ "aws-firehose.default.maxRequestHeaderSize": "8192",
+ "aws-firehose.default.port": "12801",
+ "aws-firehose.default.tlsCertChainPath": "",
+ "aws-firehose.default.tlsKeyPath": "",
+ "aws-firehose.provider": "default",
+ "cluster.provider": "standalone",
+ "configuration-discovery.default.disableMessageDigest": "false",
+ "configuration-discovery.provider": "default",
+ "configuration.provider": "none",
+ "core.default.activeExtraModelColumns": "false",
+ "core.default.autocompleteTagKeysQueryMaxSize": "100",
+ "core.default.autocompleteTagValuesQueryMaxSize": "100",
+ "core.default.dataKeeperExecutePeriod": "5",
+ "core.default.downsampling": "[Hour, Day]",
+ "core.default.enableDataKeeperExecutor": "true",
+ "core.default.enableEndpointNameGroupingByOpenapi": "true",
+ "core.default.enableHierarchy": "true",
+ "core.default.endpointNameMaxLength": "150",
+ "core.default.gRPCHost": "0.0.0.0",
+ "core.default.gRPCPort": "11800",
+ "core.default.gRPCSslCertChainPath": "",
+ "core.default.gRPCSslEnabled": "false",
+ "core.default.gRPCSslKeyPath": "",
+ "core.default.gRPCSslTrustedCAPath": "",
+ "core.default.gRPCThreadPoolSize": "-1",
+ "core.default.httpMaxRequestHeaderSize": "8192",
+ "core.default.instanceNameMaxLength": "70",
+ "core.default.l1FlushPeriod": "500",
+ "core.default.maxConcurrentCallsPerConnection": "0",
+ "core.default.maxDirectMemoryUsage": "-1",
+ "core.default.maxHeapMemoryUsagePercent": "96",
+ "core.default.maxHttpUrisNumberPerService": "3000",
+ "core.default.maxMessageSize": "52428800",
+ "core.default.metricsDataTTL": "7",
+ "core.default.persistentPeriod": "25",
+ "core.default.prepareThreads": "2",
+ "core.default.recordDataTTL": "3",
+ "core.default.restAcceptQueueSize": "0",
+ "core.default.restContextPath": "/",
+ "core.default.restHost": "0.0.0.0",
+ "core.default.restIdleTimeOut": "30000",
+ "core.default.restPort": "12800",
+ "core.default.role": "Mixed",
+ "core.default.searchableAlarmTags": "level",
+ "core.default.searchableLogsTags": "level,http.status_code,ai_route_type",
+ "core.default.searchableTracesTags":
"http.method,http.status_code,rpc.status_code,db.type,db.instance,mq.queue,mq.topic,mq.broker",
+ "core.default.serviceCacheRefreshInterval": "10",
+ "core.default.serviceNameMaxLength": "70",
+ "core.default.storageSessionTimeout": "70000",
+ "core.default.syncPeriodHttpUriRecognitionPattern": "10",
+ "core.default.topNReportPeriod": "10",
+ "core.default.trainingPeriodHttpUriRecognitionPattern": "60",
+ "core.provider": "default",
+ "dsl-debugging.default.injectionEnabled": "true",
+ "dsl-debugging.provider": "default",
+ "envoy-metric.default.acceptMetricsService": "true",
+ "envoy-metric.default.alsHTTPAnalysis": "",
+ "envoy-metric.default.alsTCPAnalysis": "",
+ "envoy-metric.default.enabledEnvoyMetricsRules": "envoy,envoy-svc-relation",
+ "envoy-metric.default.gRPCHost": "0.0.0.0",
+ "envoy-metric.default.gRPCPort": "0",
+ "envoy-metric.default.gRPCSslCertChainPath": "",
+ "envoy-metric.default.gRPCSslEnabled": "false",
+ "envoy-metric.default.gRPCSslKeyPath": "",
+ "envoy-metric.default.gRPCSslTrustedCAsPath": "",
+ "envoy-metric.default.gRPCThreadPoolSize": "0",
+ "envoy-metric.default.istioServiceEntryIgnoredNamespaces": "",
+ "envoy-metric.default.istioServiceNameRule":
"${serviceEntry.metadata.name}.${serviceEntry.metadata.namespace}",
+ "envoy-metric.default.k8sServiceNameRule":
"${pod.metadata.labels.(service.istio.io/canonical-name)}.${pod.metadata.namespace}",
+ "envoy-metric.default.maxConcurrentCallsPerConnection": "0",
+ "envoy-metric.default.maxMessageSize": "0",
+ "envoy-metric.provider": "default",
+ "event-analyzer.provider": "default",
+ "gen-ai-analyzer.provider": "default",
+ "health-checker.default.checkIntervalSeconds": "30",
+ "health-checker.provider": "default",
+ "inspect.provider": "default",
+ "log-analyzer.default.lalFiles":
"envoy-als,mesh-dp,mysql-slowsql,pgsql-slowsql,redis-slowsql,k8s-service,nginx,envoy-ai-gateway,miniprogram,default",
+ "log-analyzer.default.malFiles":
"nginx,miniprogram-wechat,miniprogram-alipay",
+ "log-analyzer.provider": "default",
+ "logql.default.restAcceptQueueSize": "0",
+ "logql.default.restContextPath": "/",
+ "logql.default.restHost": "0.0.0.0",
+ "logql.default.restIdleTimeOut": "30000",
+ "logql.default.restPort": "3100",
+ "logql.provider": "default",
+ "promql.default.buildInfoBranch": "",
+ "promql.default.buildInfoBuildDate": "",
+ "promql.default.buildInfoBuildUser": "******",
+ "promql.default.buildInfoGoVersion": "",
+ "promql.default.buildInfoRevision": "",
+ "promql.default.buildInfoVersion": "2.45.0",
+ "promql.default.restAcceptQueueSize": "0",
+ "promql.default.restContextPath": "/",
+ "promql.default.restHost": "0.0.0.0",
+ "promql.default.restIdleTimeOut": "30000",
+ "promql.default.restPort": "9090",
+ "promql.provider": "default",
+ "query.graphql.enableLogTestTool": "false",
+ "query.graphql.enableOnDemandPodLog": "false",
+ "query.graphql.maxQueryComplexity": "3000",
+ "query.provider": "graphql",
+ "receiver-async-profiler.default.jfrMaxSize": "31457280",
+ "receiver-async-profiler.default.memoryParserEnabled": "true",
+ "receiver-async-profiler.provider": "default",
+ "receiver-browser.default.sampleRate": "10000",
+ "receiver-browser.provider": "default",
+ "receiver-clr.provider": "default",
+ "receiver-ebpf.default.continuousPolicyCacheTimeout": "60",
+ "receiver-ebpf.default.gRPCHost": "0.0.0.0",
+ "receiver-ebpf.default.gRPCPort": "0",
+ "receiver-ebpf.default.gRPCSslCertChainPath": "",
+ "receiver-ebpf.default.gRPCSslEnabled": "false",
+ "receiver-ebpf.default.gRPCSslKeyPath": "",
+ "receiver-ebpf.default.gRPCSslTrustedCAsPath": "",
+ "receiver-ebpf.default.gRPCThreadPoolSize": "0",
+ "receiver-ebpf.default.maxConcurrentCallsPerConnection": "0",
+ "receiver-ebpf.default.maxMessageSize": "0",
+ "receiver-ebpf.provider": "default",
+ "receiver-event.provider": "default",
+ "receiver-jvm.provider": "default",
+ "receiver-log.provider": "default",
+ "receiver-meter.provider": "default",
+ "receiver-otel.default.enabledHandlers":
"otlp-traces,otlp-metrics,otlp-logs",
+ "receiver-otel.default.enabledOtelMetricsRules":
"apisix,nginx/*,k8s/*,istio-controlplane,vm,mysql/*,postgresql/*,oap,aws-eks/*,windows,aws-s3/*,aws-dynamodb/*,aws-gateway/*,redis/*,elasticsearch/*,rabbitmq/*,mongodb/*,kafka/*,pulsar/*,bookkeeper/*,rocketmq/*,clickhouse/*,activemq/*,kong/*,flink/*,banyandb/*,envoy-ai-gateway/*,ios/*,miniprogram/*",
+ "receiver-otel.provider": "default",
+ "receiver-pprof.default.memoryParserEnabled": "true",
+ "receiver-pprof.default.pprofMaxSize": "31457280",
+ "receiver-pprof.provider": "default",
+ "receiver-profile.provider": "default",
+ "receiver-register.provider": "default",
+ "receiver-runtime-rule.default.refreshRulesPeriod": "30",
+ "receiver-runtime-rule.default.selfHealThresholdSeconds": "60",
+ "receiver-runtime-rule.provider": "default",
+ "receiver-sharing-server.default.authentication": "******",
+ "receiver-sharing-server.default.gRPCHost": "0.0.0.0",
+ "receiver-sharing-server.default.gRPCPort": "0",
+ "receiver-sharing-server.default.gRPCSslCertChainPath": "",
+ "receiver-sharing-server.default.gRPCSslEnabled": "false",
+ "receiver-sharing-server.default.gRPCSslKeyPath": "",
+ "receiver-sharing-server.default.gRPCSslTrustedCAsPath": "",
+ "receiver-sharing-server.default.gRPCThreadPoolSize": "0",
+ "receiver-sharing-server.default.httpMaxRequestHeaderSize": "8192",
+ "receiver-sharing-server.default.maxConcurrentCallsPerConnection": "0",
+ "receiver-sharing-server.default.maxMessageSize": "52428800",
+ "receiver-sharing-server.default.restAcceptQueueSize": "0",
+ "receiver-sharing-server.default.restContextPath": "/",
+ "receiver-sharing-server.default.restHost": "0.0.0.0",
+ "receiver-sharing-server.default.restIdleTimeOut": "30000",
+ "receiver-sharing-server.default.restPort": "0",
+ "receiver-sharing-server.provider": "default",
+ "receiver-telegraf.default.activeFiles": "vm",
+ "receiver-telegraf.provider": "default",
+ "receiver-trace.provider": "default",
+ "service-mesh.provider": "default",
+ "status.default.keywords4MaskingSecretsOfConfig":
"user,password,trustStorePass,keyStorePass,token,accessKey,secretKey,authentication",
+ "status.provider": "default",
+ "storage.mysql.asyncBatchPersistentPoolSize": "4",
+ "storage.mysql.maxSizeOfBatchSql": "2000",
+ "storage.mysql.metadataQueryMaxSize": "5000",
+ "storage.mysql.properties.dataSource.cachePrepStmts": "true",
+ "storage.mysql.properties.dataSource.password": "******",
+ "storage.mysql.properties.dataSource.prepStmtCacheSize": "250",
+ "storage.mysql.properties.dataSource.prepStmtCacheSqlLimit": "2048",
+ "storage.mysql.properties.dataSource.useServerPrepStmts": "true",
+ "storage.mysql.properties.dataSource.user": "******",
+ "storage.mysql.properties.jdbcUrl":
"jdbc:mysql://mysql:3306/swtest?allowMultiQueries=true",
+ "storage.provider": "mysql",
+ "telemetry.prometheus.host": "0.0.0.0",
+ "telemetry.prometheus.port": "1234",
+ "telemetry.prometheus.sslCertChainPath": "",
+ "telemetry.prometheus.sslEnabled": "false",
+ "telemetry.prometheus.sslKeyPath": "",
+ "telemetry.provider": "prometheus",
+ "ui-management.provider": "default"
+}
diff --git a/test/e2e-v2/cases/storage/mysql/e2e.yaml
b/test/e2e-v2/cases/storage/mysql/e2e.yaml
index c6f14c5162..423c3c6024 100644
--- a/test/e2e-v2/cases/storage/mysql/e2e.yaml
+++ b/test/e2e-v2/cases/storage/mysql/e2e.yaml
@@ -69,8 +69,7 @@ verify:
| yq e '.traces[0].traceids[0]' - \
)
expected: ../expected/trace-users-detail.yml
- - query: curl -X GET http://${oap_host}:${oap_17128}/debugging/config/dump
+ - query: swctl --display json --admin-url=http://${oap_host}:${oap_17128}
admin config dump
expected: ../expected/config-dump.yml
- - query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ - query: swctl --display json --admin-url=http://${oap_host}:${oap_17128}
admin config ttl
expected: ../expected/ttl-config.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/opensearch/e2e.yaml
b/test/e2e-v2/cases/storage/opensearch/e2e.yaml
index d8bb380b29..1e07021a0b 100644
--- a/test/e2e-v2/cases/storage/opensearch/e2e.yaml
+++ b/test/e2e-v2/cases/storage/opensearch/e2e.yaml
@@ -72,5 +72,5 @@ verify:
)
expected: ../expected/trace-users-detail.yml
- query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../expected/ttl-config.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/postgres/e2e.yaml
b/test/e2e-v2/cases/storage/postgres/e2e.yaml
index 5fe085caf1..bbc791aeb1 100644
--- a/test/e2e-v2/cases/storage/postgres/e2e.yaml
+++ b/test/e2e-v2/cases/storage/postgres/e2e.yaml
@@ -70,5 +70,5 @@ verify:
)
expected: ../expected/trace-users-detail.yml
- query: |
- curl -X GET http://${oap_host}:${oap_17128}/status/config/ttl -H
"Accept: application/json"
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
config ttl
expected: ../expected/ttl-config.yml
\ No newline at end of file
diff --git a/test/e2e-v2/cases/storage/storage-cases.yaml
b/test/e2e-v2/cases/storage/storage-cases.yaml
index 36203e5677..799125791a 100644
--- a/test/e2e-v2/cases/storage/storage-cases.yaml
+++ b/test/e2e-v2/cases/storage/storage-cases.yaml
@@ -167,58 +167,61 @@ cases:
# PostgreSQL).
# =====================================================================
- # /inspect/metrics — catalog endpoint, asserts service_cpm shows the
+ # inspect metrics — catalog endpoint, asserts service_cpm shows the
# expected type / scope / catalog / supported downsamplings.
- - query: curl -s
"http://${oap_host}:${oap_17128}/inspect/metrics?regex=service_cpm" | yq -P
+ - query: swctl --display json --admin-url=http://${oap_host}:${oap_17128}
admin inspect metrics --regex service_cpm | yq -P
expected: expected/inspect-metrics.yml
- # /inspect/entities — Service scope, step=DAY (single bucket = today UTC).
+ # inspect entities — Service scope, step=DAY (single bucket = today UTC).
# Avoids the framework's MAX_TIME_RANGE=500 cap and works on any host shell
# (BSD date on macOS, GNU date in CI; both accept `date -u +%Y-%m-%d`).
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_cpm --start "${DAY}" --end "${DAY}" --step
DAY | yq -P
expected: expected/inspect-entities-service-cpm.yml
# ServiceInstance scope — instance-id decode + parent-service layer lookup.
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_instance_cpm&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_instance_cpm --start "${DAY}" --end "${DAY}"
--step DAY | yq -P
expected: expected/inspect-entities-service-instance-cpm.yml
# Endpoint scope — endpoint-id decode.
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=endpoint_cpm&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric endpoint_cpm --start "${DAY}" --end "${DAY}" --step
DAY | yq -P
expected: expected/inspect-entities-endpoint-cpm.yml
# ServiceRelation scope — analysisRelationId decode + paired
source/destination
# shape + MqeEntity dest* fields.
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_relation_client_cpm&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_relation_client_cpm --start "${DAY}" --end
"${DAY}" --step DAY | yq -P
expected: expected/inspect-entities-service-relation-cpm.yml
# LABELED_VALUE metric — Service-scope entity decode is identical but the
# storage value column shape differs (BanyanDB DataTable, ES JSON, JDBC
# VARCHAR).
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_percentile&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_percentile --start "${DAY}" --end "${DAY}"
--step DAY | yq -P
expected: expected/inspect-entities-service-percentile.yml
# step=MINUTE over a 20-minute window. Exercises minute-precision range.
# python3 keeps the date math portable across BSD / GNU date.
- query: |
- START=$(python3 -c "import datetime as d; print((d.datetime.utcnow() -
d.timedelta(minutes=20)).strftime('%Y-%m-%d %H%M'))" | sed 's/ /%20/')
- END=$(python3 -c "import datetime as d;
print(d.datetime.utcnow().strftime('%Y-%m-%d %H%M'))" | sed 's/ /%20/')
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${START}&end=${END}&step=MINUTE"
| yq -P
+ START=$(python3 -c "import datetime as d; print((d.datetime.utcnow() -
d.timedelta(minutes=20)).strftime('%Y-%m-%d %H%M'))")
+ END=$(python3 -c "import datetime as d;
print(d.datetime.utcnow().strftime('%Y-%m-%d %H%M'))")
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_cpm --start "${START}" --end "${END}" --step
MINUTE | yq -P
expected: expected/inspect-entities-service-cpm-minute.yml
# step=HOUR over a full-day window (00 → 23 UTC). Avoids the flakiness of
# a sliding 2-hour window — postgres / slow downsamplers may not have
# populated the *current* HOUR bucket yet, but at least one earlier HOUR
# bucket in today is guaranteed once the MINUTE case has data.
- query: |
- START=$(date -u +"%Y-%m-%d")%2000
- END=$(date -u +"%Y-%m-%d")%2023
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=service_cpm&start=${START}&end=${END}&step=HOUR"
| yq -P
+ DAY=$(date -u +"%Y-%m-%d")
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} admin
inspect entities --metric service_cpm --start "${DAY} 00" --end "${DAY} 23"
--step HOUR | yq -P
expected: expected/inspect-entities-service-cpm-hour.yml
- # Negative — unknown metric returns a 400-shaped error JSON.
+ # Negative — unknown metric: swctl exits non-zero and renders the typed error
+ # envelope ("... HTTP 400: unknown metric: <name>"). Reconstruct the original
+ # {error: ...} shape from the message so the expected file is unchanged.
- query: |
DAY=$(date -u +"%Y-%m-%d")
- curl -s
"http://${oap_host}:${oap_17128}/inspect/entities?metric=nonexistent_metric_xyz&start=${DAY}&end=${DAY}&step=DAY"
| yq -P
+ out=$(swctl --display yaml --admin-url=http://${oap_host}:${oap_17128}
admin inspect entities --metric nonexistent_metric_xyz --start "${DAY}" --end
"${DAY}" --step DAY 2>&1 || true)
+ msg=$(echo "$out" | grep -o 'unknown metric: [a-z_0-9]*' | head -1)
+ yq -n ".error = \"${msg}\""
expected: expected/inspect-entities-unknown-metric.yml
diff --git a/test/e2e-v2/cases/ui-management/banyandb/e2e.yaml
b/test/e2e-v2/cases/ui-management/banyandb/e2e.yaml
index e51a409176..84a8c3776f 100644
--- a/test/e2e-v2/cases/ui-management/banyandb/e2e.yaml
+++ b/test/e2e-v2/cases/ui-management/banyandb/e2e.yaml
@@ -15,7 +15,7 @@
# Exercises the ui-management REST surface end-to-end against a fresh OAP +
# BanyanDB stack. No agent traffic is needed — the test drives the admin host
-# directly with curl, walking the full template CRUD cycle.
+# directly with `swctl admin ui-template`, walking the full template CRUD
cycle.
setup:
env: compose
@@ -27,6 +27,8 @@ setup:
command: export PATH=/tmp/skywalking-infra-e2e/bin:$PATH
- name: install yq
command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh yq
+ - name: install swctl
+ command: bash test/e2e-v2/script/prepare/setup-e2e-shell/install.sh swctl
verify:
# ui-management's start() waits on BanyanDB to install the ui_template
diff --git a/test/e2e-v2/cases/ui-management/ui-management-cases.yaml
b/test/e2e-v2/cases/ui-management/ui-management-cases.yaml
index 82aca58ce0..bc572ce17f 100644
--- a/test/e2e-v2/cases/ui-management/ui-management-cases.yaml
+++ b/test/e2e-v2/cases/ui-management/ui-management-cases.yaml
@@ -13,61 +13,60 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# REST surface for dashboard templates, mounted on the admin-server REST host
-# (default :17128). The cases below walk add → get → list → change → disable →
-# list-default vs. list-with-disabled to exercise the full CRUD cycle. The
-# client supplies the template id on POST; subsequent steps read it back from
-# /tmp/uitpl-add.json.
+# Dashboard-template CRUD over the admin-server `ui-management` module, driven
by
+# `swctl admin ui-template ...` instead of raw curl. The cases walk
+# create -> get -> update -> get -> disable -> list-default vs.
list-with-disabled
+# to exercise the full cycle. The client supplies a fixed template id on
create so
+# the later steps address it without reading it back.
cases:
- # 1. POST a new template — client supplies the id.
+ # 1. Create a new template — the client supplies the id; the configuration is
+ # passed as a file (swctl reads the JSON-encoded body from --file).
- query: |
- curl -s -X POST http://${oap_host}:${oap_17128}/ui-management/templates \
- -H 'Content-Type: application/json' \
- -d
'{"id":"e2e-tpl-id","configuration":"{\"name\":\"e2e-tpl\",\"v\":1}"}' \
- | tee /tmp/uitpl-add.json \
- | yq -P '. | {"status": .status, "hasId": (.id != null and .id != "")}'
+ printf '%s' '{"name":"e2e-tpl","v":1}' > /tmp/uitpl-cfg.json
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template create --id e2e-tpl-id -f /tmp/uitpl-cfg.json \
+ | yq -P '{"status": .status, "hasId": (.id != null and .id != "")}'
expected: expected/template-write-success.yml
- # 2. GET the template by the id returned from step 1.
+ # 2. Get the template by the fixed id.
- query: |
- ID=$(yq -r '.id' /tmp/uitpl-add.json);
- curl -s -X GET
http://${oap_host}:${oap_17128}/ui-management/templates/$ID \
- | yq -P '. | {"configuration": .configuration, "disabled": .disabled}'
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template get e2e-tpl-id \
+ | yq -P '{"configuration": .configuration, "disabled": .disabled}'
expected: expected/template-fetched.yml
- # 3. PUT to update the configuration. Same id, new body.
+ # 3. Update the configuration. Same id, new body.
- query: |
- ID=$(yq -r '.id' /tmp/uitpl-add.json);
- curl -s -X PUT http://${oap_host}:${oap_17128}/ui-management/templates \
- -H 'Content-Type: application/json' \
- -d
"{\"id\":\"$ID\",\"configuration\":\"{\\\"name\\\":\\\"e2e-tpl\\\",\\\"v\\\":2}\"}"
\
- | yq -P '. | {"status": .status, "id": .id}'
+ printf '%s' '{"name":"e2e-tpl","v":2}' > /tmp/uitpl-cfg2.json
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template update --id e2e-tpl-id -f /tmp/uitpl-cfg2.json \
+ | yq -P '{"status": .status, "id": .id}'
expected: expected/template-changed.yml
- # 4. GET again — configuration should now reflect the PUT body.
+ # 4. Get again — configuration should now reflect the update.
- query: |
- ID=$(yq -r '.id' /tmp/uitpl-add.json);
- curl -s -X GET
http://${oap_host}:${oap_17128}/ui-management/templates/$ID \
- | yq -P '. | {"configuration": .configuration, "disabled": .disabled}'
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template get e2e-tpl-id \
+ | yq -P '{"configuration": .configuration, "disabled": .disabled}'
expected: expected/template-updated.yml
- # 5. POST .../disable — soft-delete.
+ # 5. Disable — soft-delete.
- query: |
- ID=$(yq -r '.id' /tmp/uitpl-add.json);
- curl -s -X POST
http://${oap_host}:${oap_17128}/ui-management/templates/$ID/disable \
- | yq -P '. | {"status": .status, "id": .id}'
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template disable e2e-tpl-id \
+ | yq -P '{"status": .status, "id": .id}'
expected: expected/template-changed.yml
# 6. Default list excludes disabled — should be empty.
- query: |
- curl -s -X GET http://${oap_host}:${oap_17128}/ui-management/templates \
- | yq -P '. | length'
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template list | yq -P '. | length'
expected: expected/template-list-empty.yml
- # 7. includingDisabled=true returns the soft-deleted entry.
+ # 7. --include-disabled returns the soft-deleted entry.
- query: |
- ID=$(yq -r '.id' /tmp/uitpl-add.json);
- curl -s -X GET
"http://${oap_host}:${oap_17128}/ui-management/templates?includingDisabled=true"
\
- | yq -P '.[] | select(.id == "'"$ID"'") | {"disabled": .disabled}'
+ swctl --display json --admin-url=http://${oap_host}:${oap_17128} \
+ admin ui-template list --include-disabled \
+ | yq -P '.[] | select(.id == "e2e-tpl-id") | {"disabled": .disabled}'
expected: expected/template-list-disabled.yml
diff --git a/test/e2e-v2/script/env b/test/e2e-v2/script/env
index 0944fddff9..d51720c7b9 100644
--- a/test/e2e-v2/script/env
+++ b/test/e2e-v2/script/env
@@ -27,7 +27,7 @@ SW_BANYANDB_COMMIT=84b919efca3fee3d51df9e97a734a9f10ae6f1d2
SW_AGENT_PHP_COMMIT=d1114e7be5d89881eec76e5b56e69ff844691e35
SW_PREDICTOR_COMMIT=54a0197654a3781a6f73ce35146c712af297c994
-SW_CTL_COMMIT=9a1beab08413ce415a00a8547a238a14691c5655
+SW_CTL_COMMIT=b447211a9319eeb29a445335e9c2536f8c1aa23d
# Third-party image versions used by e2e infrastructure (not skywalking
# components). Pinned here so the matrix is reproducible.