mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-03 03:47:42 +00:00
6d3ce90c0f
Co-authored-by: heihutu <heihutu@gmail.com>
533 lines
18 KiB
Bash
Executable File
533 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Copyright 2026 RustFS Team
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
|
|
ENDPOINT=""
|
|
ADMIN_TOKEN=""
|
|
ACCESS_KEY=""
|
|
SECRET_KEY=""
|
|
AWS_SIGV4_SCOPE="aws:amz:us-east-1:s3"
|
|
OUT_DIR=""
|
|
WINDOW_SPEC="nightly-2h:120:16:5000:balanced,nightly-12h:720:24:8000:write-heavy,nightly-24h:1440:32:12000:read-heavy"
|
|
SOAK_RATIOS="read-heavy:90:10,balanced:70:30,write-heavy:40:60"
|
|
WORKLOAD_SIZES="4KiB,1MiB,16MiB"
|
|
JOB_BUCKET="manual-transition"
|
|
JOB_PREFIX="journal-nightly-stress"
|
|
TIER=""
|
|
RUN_ADMIN_CHECKS=true
|
|
DRY_RUN=false
|
|
UNKNOWN_FAILURE_RATIO_THRESHOLD="0.00"
|
|
QUEUE_MISMATCH_RATIO_THRESHOLD="0.00"
|
|
UNKNOWN_FAILURE_COUNT_THRESHOLD="0"
|
|
|
|
STRESS_MATRIX_SCRIPT="${PROJECT_ROOT}/scripts/manual_transition_soak_matrix.sh"
|
|
RUNBOOK_FILE=""
|
|
COMMAND_FILE=""
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage:
|
|
scripts/manual_transition_nightly_stress_runbook.sh --endpoint <admin-api> [options]
|
|
|
|
Required:
|
|
--endpoint Admin API base, e.g. https://127.0.0.1:9000
|
|
|
|
Optional:
|
|
--admin-token Bearer token for admin endpoints
|
|
--access-key Access key for SigV4-signed admin calls
|
|
--secret-key Secret key for SigV4-signed admin calls
|
|
--aws-sigv4-scope curl --aws-sigv4 scope, default aws:amz:us-east-1:s3
|
|
--window-spec Comma-separated run spec: window:duration_min:concurrency:ops_per_min:mix_name
|
|
--soak-ratios Comma-separated mix ratios: label:read_pct:write_pct
|
|
--workload-sizes Object-size workload set
|
|
--job-bucket Job bucket scope for transition commands
|
|
--job-prefix Prefix for generated commands (default: journal-nightly-stress)
|
|
--tier Transition tier (default: empty)
|
|
--out-dir Output directory for generated artifacts
|
|
--no-admin-checks Omit generated admin check commands in the runbook
|
|
--dry-run Generate artifacts only; do not create executable runner
|
|
--help
|
|
|
|
Artifacts:
|
|
- nightly stress runbook markdown
|
|
- matrix/run scripts from manual_transition_soak_matrix.sh
|
|
- runnable command entry template (includes failure snapshot hook)
|
|
USAGE
|
|
}
|
|
|
|
arg_value() {
|
|
local flag="$1"
|
|
local value="${2:-}"
|
|
if [[ -z "$value" || "$value" == --* ]]; then
|
|
echo "ERROR: missing value for $flag" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s' "$value"
|
|
}
|
|
|
|
require_cmd() {
|
|
local cmd="$1"
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
echo "ERROR: required command not found: $cmd" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--endpoint)
|
|
ENDPOINT="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--admin-token)
|
|
ADMIN_TOKEN="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--access-key)
|
|
ACCESS_KEY="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--secret-key)
|
|
SECRET_KEY="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--aws-sigv4-scope)
|
|
AWS_SIGV4_SCOPE="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--window-spec)
|
|
WINDOW_SPEC="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--soak-ratios)
|
|
SOAK_RATIOS="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--workload-sizes)
|
|
WORKLOAD_SIZES="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--job-bucket)
|
|
JOB_BUCKET="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--job-prefix)
|
|
JOB_PREFIX="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--tier)
|
|
TIER="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--out-dir)
|
|
OUT_DIR="$(arg_value "$1" "${2:-}")"
|
|
shift 2
|
|
;;
|
|
--no-admin-checks)
|
|
RUN_ADMIN_CHECKS=false
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "ERROR: unknown arg: $1" >&2
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
main_matrix() {
|
|
local args=("${STRESS_MATRIX_SCRIPT}" --endpoint "$ENDPOINT")
|
|
|
|
args+=(--window-spec "$WINDOW_SPEC")
|
|
args+=(--soak-ratios "$SOAK_RATIOS")
|
|
args+=(--workload-sizes "$WORKLOAD_SIZES")
|
|
[[ -n "$ADMIN_TOKEN" ]] && args+=(--admin-token "$ADMIN_TOKEN")
|
|
[[ "$RUN_ADMIN_CHECKS" == true ]] || args+=(--no-admin-checks)
|
|
[[ -n "$OUT_DIR" ]] && args+=(--out-dir "$OUT_DIR")
|
|
[[ "$DRY_RUN" == true ]] && args+=(--dry-run)
|
|
|
|
"${args[@]}"
|
|
}
|
|
|
|
command_template() {
|
|
local matrix_csv="${OUT_DIR}/nightly_soak_matrix.csv"
|
|
local matrix_cmd="${OUT_DIR}/run_soak_matrix.sh"
|
|
local notes="${OUT_DIR}/soak_notes.md"
|
|
RUNBOOK_FILE="${OUT_DIR}/manual_transition_nightly_stress_runbook.md"
|
|
COMMAND_FILE="${OUT_DIR}/run_nightly_stress_plan.sh"
|
|
|
|
cat >"$RUNBOOK_FILE" <<'RUNBOOK'
|
|
# Manual transition nightly/stress stress-runbook
|
|
|
|
## Runtime baseline
|
|
|
|
- endpoint: __ENDPOINT__
|
|
- bucket: __JOB_BUCKET__
|
|
- prefix template: __JOB_PREFIX__
|
|
- tier: __TIER__
|
|
- matrix: __MATRIX_CSV__
|
|
- matrix command: __MATRIX_CMD__
|
|
- notes: __NOTES__
|
|
|
|
## Threshold template (editable)
|
|
|
|
- unknown failure ratio threshold: __UNKNOWN_FAILURE_RATIO_THRESHOLD__
|
|
- queue-mismatch tolerance ratio: __QUEUE_MISMATCH_RATIO_THRESHOLD__
|
|
- unknown failure count threshold: __UNKNOWN_FAILURE_COUNT_THRESHOLD__
|
|
- already-in-flight transitions: fail any terminal run with skipped_already_in_flight > 0
|
|
|
|
## Failure snapshot policy
|
|
|
|
- on startup failure (missing job_id, API error), write a timestamped snapshot under '__OUT_DIR__/failure-snapshots'
|
|
- if immediate status shows failure_reason, capture snapshot under '__OUT_DIR__/failure-snapshots'
|
|
- for each failed run, run manual_transition_journal_audit.sh and keep its outputs as evidence
|
|
- for tier-failure attribution gates, run scripts/manual_transition_failure_samples.sh with the failed job ids and keep its summary CSV with the failure snapshot
|
|
|
|
## Usage
|
|
|
|
Use the commands below directly:
|
|
|
|
- chmod +x __COMMAND_FILE__
|
|
- bash __COMMAND_FILE__
|
|
|
|
## Files produced
|
|
|
|
- __MATRIX_CSV__
|
|
- __MATRIX_CMD__
|
|
- __NOTES__
|
|
- __RUNBOOK_FILE__
|
|
- __COMMAND_FILE__
|
|
- __OUT_DIR__/failure-snapshots/*
|
|
RUNBOOK
|
|
|
|
perl -0pi -e "s#__ENDPOINT__#${ENDPOINT//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__JOB_BUCKET__#${JOB_BUCKET//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__JOB_PREFIX__#${JOB_PREFIX//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__TIER__#${TIER//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__MATRIX_CSV__#${matrix_csv//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__MATRIX_CMD__#${matrix_cmd//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__NOTES__#${notes//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__RUNBOOK_FILE__#${RUNBOOK_FILE//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__COMMAND_FILE__#${COMMAND_FILE//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__OUT_DIR__#${OUT_DIR//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__UNKNOWN_FAILURE_RATIO_THRESHOLD__#${UNKNOWN_FAILURE_RATIO_THRESHOLD//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__QUEUE_MISMATCH_RATIO_THRESHOLD__#${QUEUE_MISMATCH_RATIO_THRESHOLD//#/#}#g" "$RUNBOOK_FILE"
|
|
perl -0pi -e "s#__UNKNOWN_FAILURE_COUNT_THRESHOLD__#${UNKNOWN_FAILURE_COUNT_THRESHOLD//#/#}#g" "$RUNBOOK_FILE"
|
|
|
|
cat >"$COMMAND_FILE" <<'EOF'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SOAK_MATRIX_CSV='__SOAK_MATRIX_CSV__'
|
|
ENDPOINT='__ENDPOINT__'
|
|
ADMIN_TOKEN='__ADMIN_TOKEN__'
|
|
ACCESS_KEY='__ACCESS_KEY__'
|
|
SECRET_KEY='__SECRET_KEY__'
|
|
AWS_SIGV4_SCOPE='__AWS_SIGV4_SCOPE__'
|
|
JOB_BUCKET='__JOB_BUCKET__'
|
|
JOB_PREFIX='__JOB_PREFIX__'
|
|
TIER='__TIER__'
|
|
OUT_DIR='__OUT_DIR__'
|
|
|
|
: "${UNKNOWN_FAILURE_RATIO_THRESHOLD:=0.0}"
|
|
: "${QUEUE_MISMATCH_RATIO_THRESHOLD:=0.0}"
|
|
: "${UNKNOWN_FAILURE_COUNT_THRESHOLD:=0}"
|
|
|
|
SNAPSHOT_DIR="${OUT_DIR}/failure-snapshots"
|
|
RESULT_DIR="${OUT_DIR}/run-results"
|
|
mkdir -p "$SNAPSHOT_DIR"
|
|
mkdir -p "$RESULT_DIR"
|
|
|
|
require_cmd() {
|
|
local cmd="$1"
|
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
echo "ERROR: required command not found: $cmd" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
url_encode() {
|
|
local value="$1"
|
|
jq -rn --arg v "$value" '$v|@uri'
|
|
}
|
|
|
|
curl_admin() {
|
|
local method="$1"
|
|
local url="$2"
|
|
shift 2
|
|
local args=(-sS -X "$method")
|
|
if [[ -n "$ADMIN_TOKEN" ]]; then
|
|
args+=(-H "Authorization: Bearer ${ADMIN_TOKEN}")
|
|
elif [[ -n "$ACCESS_KEY" ]]; then
|
|
args+=(--aws-sigv4 "$AWS_SIGV4_SCOPE" --user "${ACCESS_KEY}:${SECRET_KEY}")
|
|
fi
|
|
curl "${args[@]}" "$url" "$@"
|
|
}
|
|
|
|
snapshot_failure() {
|
|
local run_tag="$1"
|
|
local reason="$2"
|
|
local job_id="$3"
|
|
local ts
|
|
local snapshot_dir
|
|
|
|
ts="$(date +%Y%m%dT%H%M%S)"
|
|
snapshot_dir="${SNAPSHOT_DIR}/${run_tag}/${ts}"
|
|
mkdir -p "$snapshot_dir"
|
|
|
|
{
|
|
echo "run_tag=${run_tag}"
|
|
echo "reason=${reason}"
|
|
echo "job_id=${job_id}"
|
|
echo "ts=${ts}"
|
|
} >"${snapshot_dir}/snapshot.meta"
|
|
|
|
if [[ -n "$job_id" && "$job_id" != "NA" ]]; then
|
|
curl_admin GET "${ENDPOINT%/}/rustfs/admin/v3/ilm/transition/jobs/${job_id}" \
|
|
>"${snapshot_dir}/job_status.json"
|
|
./scripts/manual_transition_journal_audit.sh --endpoint "$ENDPOINT" --job-id "$job_id" ${ADMIN_TOKEN:+--admin-token "$ADMIN_TOKEN"} --out-dir "$snapshot_dir" || true
|
|
fi
|
|
|
|
cp "$SOAK_MATRIX_CSV" "${snapshot_dir}/source_matrix.csv"
|
|
}
|
|
|
|
run_entry() {
|
|
local tag="$1"
|
|
local duration_min="$2"
|
|
local concurrency="$3"
|
|
local ops_per_min="$4"
|
|
local mix_name="$5"
|
|
local read_pct="$6"
|
|
local write_pct="$7"
|
|
local size="$8"
|
|
local expected_ops="$9"
|
|
local budget_status="${10}"
|
|
|
|
local prefix
|
|
local query
|
|
local url
|
|
local response
|
|
local job_id
|
|
local status
|
|
local status_json
|
|
local status_info
|
|
local result_tag
|
|
|
|
if [[ "$budget_status" == "over-budget" ]]; then
|
|
echo "[skip] ${tag} ${size} over budget: expected_ops=${expected_ops}"
|
|
return 0
|
|
fi
|
|
|
|
prefix="${JOB_PREFIX}/${tag}/${size}/${read_pct}r${write_pct}w"
|
|
result_tag="${tag}-${size}-${read_pct}r${write_pct}w"
|
|
query="bucket=$(url_encode "$JOB_BUCKET")"
|
|
query="${query}&prefix=$(url_encode "$prefix")"
|
|
query="${query}&maxObjects=100000"
|
|
query="${query}&maxDurationSeconds=$((duration_min * 60))"
|
|
query="${query}&mode=async&dryRun=false"
|
|
if [[ -n "$TIER" ]]; then
|
|
query="${query}&tier=$(url_encode "$TIER")"
|
|
fi
|
|
|
|
url="${ENDPOINT%/}/rustfs/admin/v3/ilm/transition/run?${query}"
|
|
|
|
echo "==> run=${tag} size=${size} mix=${mix_name} read_pct=${read_pct} write_pct=${write_pct} duration_min=${duration_min}"
|
|
if ! response="$(curl_admin POST "$url")"; then
|
|
snapshot_failure "$tag" "curl_post_failed" "NA"
|
|
return 1
|
|
fi
|
|
printf '%s' "$response" >"${RESULT_DIR}/${result_tag}-run.json"
|
|
job_id="$(printf '%s' "$response" | jq -r '.job_id // empty')"
|
|
if [[ -z "$job_id" || "$job_id" == "null" ]]; then
|
|
snapshot_failure "$tag" "missing_job_id" "NA"
|
|
return 1
|
|
fi
|
|
|
|
if ! status_json="$(curl_admin GET "${ENDPOINT%/}/rustfs/admin/v3/ilm/transition/jobs/${job_id}")"; then
|
|
snapshot_failure "$tag" "job_status_fetch_failed" "$job_id"
|
|
return 1
|
|
fi
|
|
printf '%s' "$status_json" >"${RESULT_DIR}/${result_tag}-status.json"
|
|
if ! status_info="$(printf '%s' "$status_json" | jq -r '[.status // "", .failure_reason // "__none__", (.report.enqueued // 0), (.report.transition_completed // 0), (.report.transition_failed // 0), (.report.skipped_already_in_flight // 0), (.report.tier_failure_by_reason["unknown"] // 0), (.queue_snapshot.queued // 0), (.queue_snapshot.active // 0), (.queue_snapshot.compensation_pending // 0), (.queue_snapshot.compensation_running // 0), (.queue_snapshot.queue_full // 0), (.queue_snapshot.queue_send_timeout // 0)] | map(tostring) | @tsv')"; then
|
|
snapshot_failure "$tag" "invalid_job_status_json" "$job_id"
|
|
return 1
|
|
fi
|
|
IFS=$'\t' read -r status failure_reason report_enqueued report_completed report_failed report_already_in_flight report_unknown_failure queue_snapshot_queued queue_snapshot_active compensation_pending compensation_running queue_full queue_send_timeout <<< "$status_info"
|
|
|
|
if [[ "$failure_reason" != "__none__" ]]; then
|
|
snapshot_failure "$tag" "failure_reason=${failure_reason}" "$job_id"
|
|
fi
|
|
|
|
# Threshold checks are applied to terminal job statuses only to avoid transient noise
|
|
# from active windows that are still processing.
|
|
if is_terminal_status "$status"; then
|
|
local mismatch_count
|
|
local mismatch_ratio
|
|
local unknown_ratio
|
|
local ratio_denominator
|
|
if (( report_already_in_flight > 0 )); then
|
|
snapshot_failure "$tag" "already_in_flight=${report_already_in_flight}" "$job_id"
|
|
return 1
|
|
fi
|
|
mismatch_count="$((report_enqueued - report_completed - report_failed))"
|
|
if (( mismatch_count < 0 )); then
|
|
mismatch_count=0
|
|
fi
|
|
mismatch_count="$((queue_snapshot_queued + queue_snapshot_active + compensation_pending + compensation_running + queue_full + queue_send_timeout + mismatch_count))"
|
|
|
|
if (( expected_ops > 0 )); then
|
|
ratio_denominator="$expected_ops"
|
|
else
|
|
ratio_denominator="$report_enqueued"
|
|
fi
|
|
if (( ratio_denominator <= 0 )); then
|
|
ratio_denominator=0
|
|
fi
|
|
|
|
if (( ratio_denominator > 0 )); then
|
|
unknown_ratio="$(awk -v unknown="$report_unknown_failure" -v denom="$ratio_denominator" 'BEGIN{printf "%.6f", (unknown / denom)}')"
|
|
mismatch_ratio="$(awk -v mismatch="$mismatch_count" -v denom="$ratio_denominator" 'BEGIN{printf "%.6f", (mismatch / denom)}')"
|
|
|
|
if awk "BEGIN{exit !(${UNKNOWN_FAILURE_RATIO_THRESHOLD} > 0 && $unknown_ratio > ${UNKNOWN_FAILURE_RATIO_THRESHOLD})}"; then
|
|
snapshot_failure "$tag" "unknown_failure_ratio=${unknown_ratio}/expected=${ratio_denominator}/threshold=${UNKNOWN_FAILURE_RATIO_THRESHOLD}" "$job_id"
|
|
return 1
|
|
fi
|
|
|
|
if (( UNKNOWN_FAILURE_COUNT_THRESHOLD > 0 )) && (( report_unknown_failure > UNKNOWN_FAILURE_COUNT_THRESHOLD )); then
|
|
snapshot_failure "$tag" "unknown_failure_count=${report_unknown_failure}/threshold=${UNKNOWN_FAILURE_COUNT_THRESHOLD}" "$job_id"
|
|
return 1
|
|
fi
|
|
|
|
if awk "BEGIN{exit !(${QUEUE_MISMATCH_RATIO_THRESHOLD} > 0 && $mismatch_ratio > ${QUEUE_MISMATCH_RATIO_THRESHOLD})}"; then
|
|
snapshot_failure "$tag" "queue_mismatch_ratio=${mismatch_ratio}/expected=${ratio_denominator}/threshold=${QUEUE_MISMATCH_RATIO_THRESHOLD}" "$job_id"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Keep compatibility with older callers that monitor queue drift via full terminal status.
|
|
printf '%s' "$status_json" | jq '{job_id, status, bucket, prefix, tier, report, queue_snapshot, failure_reason}'
|
|
}
|
|
|
|
is_terminal_status() {
|
|
local current_status="$1"
|
|
|
|
case "$current_status" in
|
|
completed|partial|failed|cancelled|unknown)
|
|
return 0
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main() {
|
|
require_cmd bash
|
|
require_cmd curl
|
|
require_cmd jq
|
|
require_cmd awk
|
|
|
|
if [[ -n "$ADMIN_TOKEN" && ( -n "$ACCESS_KEY" || -n "$SECRET_KEY" ) ]]; then
|
|
echo "ERROR: bearer token cannot be combined with SigV4 credentials" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$ACCESS_KEY" && -z "$SECRET_KEY" || -z "$ACCESS_KEY" && -n "$SECRET_KEY" ]]; then
|
|
echo "ERROR: ACCESS_KEY and SECRET_KEY must be provided together" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$SOAK_MATRIX_CSV" ]]; then
|
|
echo "ERROR: expected matrix file missing: $SOAK_MATRIX_CSV" >&2
|
|
echo "Run manual_transition_soak_matrix.sh first." >&2
|
|
exit 1
|
|
fi
|
|
|
|
local failures=0
|
|
while IFS=',' read -r window duration_min concurrency ops_per_min mix_name read_pct write_pct size expected_ops run_id budget_status; do
|
|
if [[ -z "$window" || "$window" == "window" ]]; then
|
|
continue
|
|
fi
|
|
if ! run_entry "${window}" "${duration_min}" "${concurrency}" "${ops_per_min}" "${mix_name}" "${read_pct}" "${write_pct}" "${size}" "${expected_ops}" "${budget_status}"; then
|
|
failures=$((failures + 1))
|
|
fi
|
|
done < <(tail -n +2 "$SOAK_MATRIX_CSV")
|
|
if (( failures > 0 )); then
|
|
echo "ERROR: ${failures} soak row(s) failed; see ${SNAPSHOT_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
main "$@"
|
|
EOF
|
|
|
|
perl -0pi -e "s#__SOAK_MATRIX_CSV__#${matrix_csv//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__ENDPOINT__#${ENDPOINT//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__ADMIN_TOKEN__#${ADMIN_TOKEN//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__ACCESS_KEY__#${ACCESS_KEY//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__SECRET_KEY__#${SECRET_KEY//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__AWS_SIGV4_SCOPE__#${AWS_SIGV4_SCOPE//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__JOB_BUCKET__#${JOB_BUCKET//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__JOB_PREFIX__#${JOB_PREFIX//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__TIER__#${TIER//#/#}#g" "$COMMAND_FILE"
|
|
perl -0pi -e "s#__OUT_DIR__#${OUT_DIR//#/#}#g" "$COMMAND_FILE"
|
|
chmod +x "$COMMAND_FILE"
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
if [[ -z "$ENDPOINT" ]]; then
|
|
echo "ERROR: --endpoint is required" >&2
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "$OUT_DIR" ]]; then
|
|
OUT_DIR="${PROJECT_ROOT}/target/manual-transition-nightly-stress-runbook/$(date +%Y%m%dT%H%M%S)"
|
|
fi
|
|
mkdir -p "$OUT_DIR"
|
|
require_cmd awk
|
|
require_cmd jq
|
|
if [[ -n "$ADMIN_TOKEN" && ( -n "$ACCESS_KEY" || -n "$SECRET_KEY" ) ]]; then
|
|
echo "ERROR: --admin-token cannot be combined with --access-key/--secret-key" >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$ACCESS_KEY" && -z "$SECRET_KEY" || -z "$ACCESS_KEY" && -n "$SECRET_KEY" ]]; then
|
|
echo "ERROR: --access-key and --secret-key must be provided together" >&2
|
|
exit 1
|
|
fi
|
|
|
|
main_matrix
|
|
command_template
|
|
|
|
echo "Generated runbook: ${RUNBOOK_FILE}"
|
|
}
|
|
|
|
main "$@"
|