fix: close internal semantic review gaps
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Shared, daemon-scoped lock for operations that stop or replace one Compose Qdrant volume.
|
||||
# The stopped container name is the atomic primitive; ownership labels prevent a cleanup trap
|
||||
# from deleting a lock that it did not create.
|
||||
vector_operation_lock_init() {
|
||||
operation_lock_name="${expected_volume_name}-operation-lock"
|
||||
operation_lock_owner="${project_name}-$$-$(date -u +%Y%m%dT%H%M%S)"
|
||||
operation_lock_acquired=0
|
||||
}
|
||||
|
||||
acquire_vector_operation_lock() {
|
||||
if docker create \
|
||||
--name "$operation_lock_name" \
|
||||
--label "com.thothii.qdrant-operation-owner=$operation_lock_owner" \
|
||||
--label "com.thothii.qdrant-operation-project=$project_name" \
|
||||
--label "com.thothii.qdrant-operation-volume=$expected_volume_name" \
|
||||
"$helper_image" /bin/true >/dev/null 2>&1; then
|
||||
operation_lock_acquired=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if docker inspect "$operation_lock_name" >/dev/null 2>&1; then
|
||||
echo "Qdrant operation already in progress for $expected_volume_name" >&2
|
||||
else
|
||||
echo "Unable to acquire Qdrant operation lock for $expected_volume_name" >&2
|
||||
fi
|
||||
return 2
|
||||
}
|
||||
|
||||
release_vector_operation_lock() {
|
||||
[ "${operation_lock_acquired:-0}" -eq 1 ] || return 0
|
||||
metadata=$(docker inspect --format \
|
||||
'{{ index .Config.Labels "com.thothii.qdrant-operation-owner" }} {{ index .Config.Labels "com.thothii.qdrant-operation-volume" }}' \
|
||||
"$operation_lock_name" 2>/dev/null || true)
|
||||
set -- $metadata
|
||||
if [ "${1-}" != "$operation_lock_owner" ] || [ "${2-}" != "$expected_volume_name" ]; then
|
||||
echo "Qdrant operation lock ownership changed; refusing to remove it" >&2
|
||||
operation_lock_acquired=0
|
||||
return 1
|
||||
fi
|
||||
if ! docker rm -f "$operation_lock_name" >/dev/null; then
|
||||
echo "Unable to release Qdrant operation lock for $expected_volume_name" >&2
|
||||
operation_lock_acquired=0
|
||||
return 1
|
||||
fi
|
||||
operation_lock_acquired=0
|
||||
}
|
||||
@@ -3,7 +3,17 @@ set -eu
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
||||
holder_pid=
|
||||
holder_release="$tmp/holder-release"
|
||||
cleanup() {
|
||||
touch "$holder_release"
|
||||
if [ -n "${holder_pid:-}" ]; then
|
||||
kill "$holder_pid" 2>/dev/null || true
|
||||
wait "$holder_pid" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$tmp"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
fakebin="$tmp/bin"
|
||||
mkdir "$fakebin"
|
||||
|
||||
@@ -28,6 +38,15 @@ run_backup() {
|
||||
volume_name=$1
|
||||
backup_dir=$2
|
||||
output_name=$3
|
||||
if [ -n "${RUN_BLOCK_READY:-}" ]; then
|
||||
: >"$RUN_BLOCK_READY"
|
||||
attempts=0
|
||||
while [ ! -e "${RUN_BLOCK_RELEASE:?}" ]; do
|
||||
attempts=$((attempts + 1))
|
||||
[ "$attempts" -lt 400 ] || exit 24
|
||||
sleep 0.05
|
||||
done
|
||||
fi
|
||||
staging=$(mktemp -d "${TMPDIR:-/tmp}/fake-qdrant-backup.XXXXXX")
|
||||
mkdir -p "$staging/payload"
|
||||
cat >"$staging/manifest.env" <<EOF
|
||||
@@ -36,12 +55,59 @@ project_name=${PROJECT_NAME:?}
|
||||
volume_name=$volume_name
|
||||
volume_role=qdrant-data
|
||||
helper_image=${HELPER_IMAGE:?}
|
||||
created_utc=2026-08-08T17:35:36Z
|
||||
EOF
|
||||
cp -R "$(volume_dir_for "$volume_name")"/. "$staging/payload"/
|
||||
tar -C "$staging" -cf "$backup_dir/$output_name" manifest.env payload
|
||||
rm -rf "$staging"
|
||||
}
|
||||
|
||||
lock_state_root=${LOCK_ROOT:-$(dirname "${VOLUME_ROOT:?}")/locks}
|
||||
mkdir -p "$lock_state_root"
|
||||
|
||||
if [ "$1" = create ]; then
|
||||
shift
|
||||
lock_name=
|
||||
lock_owner=
|
||||
lock_volume=
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--name) lock_name=$2; shift 2 ;;
|
||||
--label)
|
||||
case "$2" in
|
||||
com.thothii.qdrant-operation-owner=*) lock_owner=${2#*=} ;;
|
||||
com.thothii.qdrant-operation-volume=*) lock_volume=${2#*=} ;;
|
||||
esac
|
||||
shift 2
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$lock_name" ] && [ -n "$lock_owner" ] && [ -n "$lock_volume" ] || exit 25
|
||||
state="$lock_state_root/$lock_name"
|
||||
mkdir "$state" 2>/dev/null || exit 1
|
||||
printf '%s\n' "$lock_owner" >"$state/owner"
|
||||
printf '%s\n' "$lock_volume" >"$state/volume"
|
||||
printf '%s\n' "$lock_name"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" = inspect ]; then
|
||||
for lock_name in "$@"; do :; done
|
||||
state="$lock_state_root/$lock_name"
|
||||
[ -d "$state" ] || exit 1
|
||||
printf '%s %s\n' "$(cat "$state/owner")" "$(cat "$state/volume")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$1" = rm ]; then
|
||||
for lock_name in "$@"; do :; done
|
||||
state="$lock_state_root/$lock_name"
|
||||
[ -d "$state" ] || exit 1
|
||||
rm -rf "$state"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
run_restore() {
|
||||
volume_name=$1
|
||||
backup_dir=$2
|
||||
@@ -219,6 +285,7 @@ PY
|
||||
|
||||
backup_output="$tmp/qdrant-backup.tar"
|
||||
docker_log="$tmp/docker-backup.log"
|
||||
lock_name="${project}_qdrant-data-operation-lock"
|
||||
PATH="$fakebin:$PATH" DOCKER_LOG="$docker_log" PROJECT_NAME="$project" VOLUME_ROOT="$volume_root" \
|
||||
HELPER_IMAGE="qdrant/qdrant:v1.18.2@sha256:75eab8c4ba42096724fdcfde8b4de0b5713d529dde32f285a1f86fdcb2c9e50c" \
|
||||
./scripts/vector-backup.sh --project-name "$project" --output "$backup_output" >/dev/null
|
||||
@@ -231,6 +298,15 @@ tar -xOf "$backup_output" manifest.env | grep -qx "volume_name=$volume_name"
|
||||
grep -q "run --rm --mount type=volume,src=$volume_name,dst=/qdrant-data,readonly --mount type=bind,src=$tmp,dst=/backup" "$docker_log"
|
||||
grep -q "compose --project-name $project stop qdrant" "$docker_log"
|
||||
grep -q "compose --project-name $project start qdrant" "$docker_log"
|
||||
grep -q "create --name $lock_name" "$docker_log"
|
||||
grep -q "rm -f $lock_name" "$docker_log"
|
||||
test ! -e "$tmp/locks/$lock_name"
|
||||
lock_line=$(grep -n "create --name $lock_name" "$docker_log" | sed -n '1s/:.*//p')
|
||||
volume_line=$(grep -n '^volume ls ' "$docker_log" | sed -n '1s/:.*//p')
|
||||
[ "$lock_line" -lt "$volume_line" ] || {
|
||||
echo "backup resolved volume state before acquiring the operation lock" >&2
|
||||
exit 1
|
||||
}
|
||||
if grep -q "volume inspect --format {{ .Mountpoint }}" "$docker_log"; then
|
||||
echo "backup consulted Docker mountpoints" >&2
|
||||
exit 1
|
||||
@@ -240,6 +316,61 @@ if grep -q "prune" "$docker_log"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$tmp/locks/$lock_name"
|
||||
printf '%s\n' foreign-owner >"$tmp/locks/$lock_name/owner"
|
||||
printf '%s\n' "$volume_name" >"$tmp/locks/$lock_name/volume"
|
||||
foreign_lock_log="$tmp/foreign-lock.log"
|
||||
if PATH="$fakebin:$PATH" DOCKER_LOG="$foreign_lock_log" PROJECT_NAME="$project" VOLUME_ROOT="$volume_root" \
|
||||
HELPER_IMAGE="qdrant/qdrant:v1.18.2@sha256:75eab8c4ba42096724fdcfde8b4de0b5713d529dde32f285a1f86fdcb2c9e50c" \
|
||||
./scripts/vector-backup.sh --project-name "$project" --output "$tmp/foreign-lock.tar" \
|
||||
>"$tmp/foreign-lock.out" 2>"$tmp/foreign-lock.err"; then
|
||||
echo "backup ignored a foreign operation lock" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'operation already in progress' "$tmp/foreign-lock.err"
|
||||
test "$(cat "$tmp/locks/$lock_name/owner")" = foreign-owner
|
||||
rm -rf "$tmp/locks/$lock_name"
|
||||
|
||||
holder_ready="$tmp/holder-ready"
|
||||
holder_output="$tmp/holder.tar"
|
||||
holder_log="$tmp/holder.log"
|
||||
PATH="$fakebin:$PATH" DOCKER_LOG="$holder_log" PROJECT_NAME="$project" VOLUME_ROOT="$volume_root" \
|
||||
RUN_BLOCK_READY="$holder_ready" RUN_BLOCK_RELEASE="$holder_release" \
|
||||
HELPER_IMAGE="qdrant/qdrant:v1.18.2@sha256:75eab8c4ba42096724fdcfde8b4de0b5713d529dde32f285a1f86fdcb2c9e50c" \
|
||||
./scripts/vector-backup.sh --project-name "$project" --output "$holder_output" >/dev/null 2>"$tmp/holder.err" &
|
||||
holder_pid=$!
|
||||
attempts=0
|
||||
while [ ! -e "$holder_ready" ]; do
|
||||
attempts=$((attempts + 1))
|
||||
[ "$attempts" -lt 400 ] || { echo "lock holder did not reach copy phase" >&2; exit 1; }
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
for contender in backup restore; do
|
||||
contender_log="$tmp/contender-$contender.log"
|
||||
if [ "$contender" = backup ]; then
|
||||
set -- ./scripts/vector-backup.sh --project-name "$project" --output "$tmp/contender.tar"
|
||||
else
|
||||
set -- ./scripts/vector-restore.sh --project-name "$project" --input "$backup_output" --confirm-project "$project"
|
||||
fi
|
||||
if PATH="$fakebin:$PATH" DOCKER_LOG="$contender_log" PROJECT_NAME="$project" VOLUME_ROOT="$volume_root" \
|
||||
HELPER_IMAGE="qdrant/qdrant:v1.18.2@sha256:75eab8c4ba42096724fdcfde8b4de0b5713d529dde32f285a1f86fdcb2c9e50c" \
|
||||
"$@" >"$tmp/contender-$contender.out" 2>"$tmp/contender-$contender.err"; then
|
||||
echo "$contender interleaved with an active qdrant operation" >&2
|
||||
exit 1
|
||||
fi
|
||||
grep -q 'operation already in progress' "$tmp/contender-$contender.err"
|
||||
if grep -Eq '^volume (ls|inspect)|^compose .* (stop|start) qdrant|^run ' "$contender_log"; then
|
||||
echo "$contender touched qdrant state after lock contention" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
touch "$holder_release"
|
||||
wait "$holder_pid"
|
||||
holder_pid=
|
||||
test -s "$holder_output"
|
||||
test ! -e "$tmp/locks/$lock_name"
|
||||
|
||||
existing="$tmp/existing.tar"
|
||||
printf '%s' sentinel >"$existing"
|
||||
if PATH="$fakebin:$PATH" DOCKER_LOG="$tmp/existing.log" PROJECT_NAME="$project" VOLUME_ROOT="$volume_root" \
|
||||
@@ -392,5 +523,6 @@ fi
|
||||
test "$(cat "$volume_dir/collections/demo/state.json")" = rollback-source
|
||||
grep -q "compose --project-name $project stop qdrant" "$rollback_log"
|
||||
grep -q "compose --project-name $project start qdrant" "$rollback_log"
|
||||
test ! -e "$tmp/locks/$lock_name"
|
||||
|
||||
echo "qdrant backup/restore archive validation, scoped helper execution, and rollback safety passed."
|
||||
|
||||
@@ -28,6 +28,9 @@ output_name=$(basename "$output")
|
||||
[ -d "$output_dir" ] || { echo "backup destination directory does not exist" >&2; exit 2; }
|
||||
|
||||
expected_volume_name="${project_name}_${volume_role}"
|
||||
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
. "$script_dir/lib/vector-operation-lock.sh"
|
||||
vector_operation_lock_init
|
||||
|
||||
resolve_volume() {
|
||||
names=$(docker volume ls \
|
||||
@@ -55,24 +58,32 @@ validate_volume_metadata() {
|
||||
[ "${3-}" = "$volume_role" ] || { echo "volume metadata role label mismatch" >&2; exit 2; }
|
||||
}
|
||||
|
||||
volume_name=$(resolve_volume)
|
||||
validate_volume_metadata "$volume_name"
|
||||
|
||||
running_container=$(docker compose --project-name "$project_name" ps --status running -q qdrant)
|
||||
restart_qdrant=0
|
||||
temporary_output=
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [ -n "${temporary_output:-}" ] && [ -e "${temporary_output:-}" ]; then
|
||||
rm -f "$temporary_output"
|
||||
fi
|
||||
if [ "$restart_qdrant" -eq 1 ]; then
|
||||
docker compose --project-name "$project_name" start qdrant >/dev/null
|
||||
if ! docker compose --project-name "$project_name" start qdrant >/dev/null; then
|
||||
echo "Unable to restart Qdrant after backup" >&2
|
||||
[ "$status" -ne 0 ] || status=1
|
||||
fi
|
||||
fi
|
||||
if ! release_vector_operation_lock; then
|
||||
[ "$status" -ne 0 ] || status=1
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT HUP INT TERM
|
||||
|
||||
acquire_vector_operation_lock
|
||||
volume_name=$(resolve_volume)
|
||||
validate_volume_metadata "$volume_name"
|
||||
running_container=$(docker compose --project-name "$project_name" ps --status running -q qdrant)
|
||||
|
||||
if [ -n "$running_container" ]; then
|
||||
docker compose --project-name "$project_name" stop qdrant >/dev/null
|
||||
restart_qdrant=1
|
||||
|
||||
@@ -30,15 +30,26 @@ done
|
||||
[ -r "$input" ] || { echo "backup input is not readable" >&2; exit 2; }
|
||||
|
||||
expected_volume_name="${project_name}_${volume_role}"
|
||||
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
. "$script_dir/lib/vector-operation-lock.sh"
|
||||
vector_operation_lock_init
|
||||
private_archive_dir=
|
||||
private_archive_path=
|
||||
restart_qdrant=0
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - EXIT HUP INT TERM
|
||||
if [ -n "${private_archive_dir:-}" ] && [ -d "${private_archive_dir:-}" ]; then
|
||||
rm -rf "$private_archive_dir"
|
||||
fi
|
||||
if [ "${restart_qdrant:-0}" -eq 1 ]; then
|
||||
docker compose --project-name "$project_name" start qdrant >/dev/null
|
||||
if ! docker compose --project-name "$project_name" start qdrant >/dev/null; then
|
||||
echo "Unable to restart Qdrant after restore" >&2
|
||||
[ "$status" -ne 0 ] || status=1
|
||||
fi
|
||||
fi
|
||||
if ! release_vector_operation_lock; then
|
||||
[ "$status" -ne 0 ] || status=1
|
||||
fi
|
||||
exit "$status"
|
||||
}
|
||||
@@ -219,11 +230,11 @@ validate_archive_paths
|
||||
validate_archive_types
|
||||
validate_archive_manifest
|
||||
|
||||
acquire_vector_operation_lock
|
||||
volume_name=$(resolve_volume)
|
||||
validate_volume_metadata "$volume_name"
|
||||
|
||||
running_container=$(docker compose --project-name "$project_name" ps --status running -q qdrant)
|
||||
restart_qdrant=0
|
||||
|
||||
if [ -n "$running_container" ]; then
|
||||
docker compose --project-name "$project_name" stop qdrant >/dev/null
|
||||
|
||||
Reference in New Issue
Block a user