Skip to content

Instantly share code, notes, and snippets.

@RobertKrawitz
Last active September 3, 2025 20:38
Show Gist options
  • Save RobertKrawitz/88b43e755d43002ccf88a96976f9c865 to your computer and use it in GitHub Desktop.
Save RobertKrawitz/88b43e755d43002ccf88a96976f9c865 to your computer and use it in GitHub Desktop.
Find one unused disk on each OCP node supporting storage for use in e. g. creating a LocalVolumeSet
#!/bin/bash
declare args=()
declare nodes=
declare -i verbose=0
declare label=cluster.ocs.openshift.io/openshift-storage
read -r -d '' script <<'EOF'
#!/bin/bash
declare -a sata_drives
declare -a nvme_drives
declare -i minsize=1500
declare -i maxsize=0
declare -i doit=0
declare -i verbose=0
declare devdir=
declare -i all_drives=0
declare -i status=0
declare -i found=0
help() {
if [[ -z "$*" ]] ; then echo "Usage: $0 args"; fi
cat <<EOD
-a Check for all matching drives
-t Print the by-path name of the drive
-i Print a by-id name of the drive
-s Check for matching SATA drives
-S Don't check for SATA drives
-p Check for matching NVMe drives
-P Don't check for NVMe drives
-z Zap the found drive
-Z Print the command, but don't zap the drive
-n Only print the name of the found drive
-v Print verbose debugging messages
-m minsize Only look at drives at least as big as minsize GB
-M maxsize Only look at drives no bigger than maxsize GB
EOD
exit 1
}
if [[ -n "${___HELP___}" ]] ; then
help 1
fi
while getopts 'sSpPvzZnm:M:tiah' opt ; do
case "$opt" in
s) sata_drives=(/dev/sd?) ;;
S) sata_drives=() ;;
p) nvme_drives=(/dev/nvme?n1) ;;
P) nvme_drives=() ;;
m) minsize=$OPTARG ;;
M) maxsize=$OPTARG ;;
z) doit=2 ;;
Z) doit=1 ;;
n) doit=0 ;;
t) devdir=by-path ;;
i) devdir=by-id ;;
a) all_drives=1 ;;
v) verbose=1 ;;
h) help ;;
*)
echo "Unknown option $opt"
help
;;
esac
done
shift $((OPTIND-1))
docmd() {
echo "$*" 1>&2
if ((doit > 1)) ; then
#"$@"
return $!
fi
}
print_drive() {
local dev=$1
if [[ -n "${devdir:-}" ]] ; then
local path
for path in /dev/disk/"$devdir"/* ; do
if [[ $(readlink -f "$path") = "$dev" ]] ; then
echo "$path"
break
fi
done
else
echo "$dev"
fi
}
doit() {
local dev="${1:-}"
if [[ ! -b "$dev" ]] ; then
echo "$dev: not block device"
exit 1
fi
case "$((doit))" in
0) print_drive "$f" ;;
*) docmd podman run -authfile /var/lib/kubelet --rm --privileged --device "$f" --entrypoint ceph-bluestore-tool quay.io/ceph/ceph:v19 zap-device --dev "$f" --yes-i-really-really-mean-it
esac
}
vecho() {
if ((verbose)) ; then echo "$*" 1>&2 ; fi
}
vecho "> Starting search on $(hostname)"
for f in "${sata_drives[@]}" "${nvme_drives[@]}" ; do
vecho ">> Looking at $f"
vecho ">>> Checking for mounted drive"
mountpoints="$(lsblk -J "$f" | jq -r '.blockdevices[0]?.mountpoints[]? | select(. != null)')"
if [[ -n "$mountpoints" ]] ; then
# shellcheck disable=SC2086
vecho ">>>> SKIPPING: $f already mounted: " $mountpoints
continue
fi
vecho ">>> Checking for mounted partitions"
cmountpoints="$(lsblk -J "$f" | jq -r '[.blockdevices[0]?.children[]?.mountpoints[]?] | flatten[] | select(. != null)')"
if [[ -n "$cmountpoints" ]] ; then
# shellcheck disable=SC2086
vecho ">>>> SKIPPING: $f has mounted partitions: " $cmountpoints
continue
fi
vecho ">>> Checking for unmounted partitions"
children="$(lsblk -J "$f" | jq -r '.blockdevices[0]?.children[]?.name | select (. != null)')"
if [[ -n "$children" ]] ; then
# shellcheck disable=SC2086
vecho ">>>> SKIPPING: $f has unmounted partitions: " $children
continue
fi
# If we immediately continued after finding a used disk
# the find would get a broken pipe, which is ugly
# albeit harmless
failed=0
if [[ -d /mnt/local-storage ]] ; then
while read -r l ; do
vecho ">>> Looking at local storage entry $l ($(readlink -f "$l"))"
if [[ $(readlink -f "$l") = "$f" ]] ; then
vecho ">>>> SKIPPING: $f is target of $l ($(readlink -f "$l"))"
failed=1
fi
done <<< "$(sudo find /mnt/local-storage -type l -print)"
if ((failed)) ; then continue; fi
fi
size="$(lsblk -J "$f" | jq -r '.blockdevices[0].size')"
if [[ $size =~ ([0-9]+(\.[0-9]+)?)([KMGTP])? ]] ; then
dsize=${BASH_REMATCH[1]}
suffix=${BASH_REMATCH[3]}
case "$suffix" in
K) scale=1000 ;;
M) scale=$((1000 * 1000)) ;;
G) scale=$((1000 * 1000 * 1000)) ;;
T) scale=$((1000 * 1000 * 1000 * 1000)) ;;
P) scale=$((1000 * 1000 * 1000 * 1000 * 1000)) ;;
*) continue ;;
esac
bytes=$(python3 <<< "print('%d' % ($dsize * $scale))")
vecho ">>> Checking size"
if ((bytes < minsize * 1000 * 1000 * 1000 || (maxsize > 0 && bytes > maxsize * 1000 * 1000 * 1000))) ; then
vecho ">>>> SKIPPING: $dsize$suffix does not match $minsize < $bytes <= $maxsize"
continue
fi
vecho ">> $f passes all checks"
if ((all_drives)) ; then
doit "$f"
case "$!" in
0) found=$((found+1)) ;;
*) status=1 ;;
esac
else
doit "$f"
exit $!
fi
fi
done
exit "$status"
EOF
help() {
cat <<EOD
Usage: $0 <args> <nodes>
Nodes default to all nodes labeled 'cluster.ocs.openshift.io/openshift-storage'
$(___HELP___=1; eval "$script")
-l label Use only nodes with the specified label.
EOD
exit
}
while getopts 'm:M:l:abcdefghijknopqrstuvwxyzABCDEFGHIJKLNOPQRSTUVWXYZ' opt ; do
case "$opt" in
m|M) args+=("-$opt" "$OPTARG") ;;
l) label=$OPTARG ;;
v) verbose=1; args+=("-$opt");;
h|H) help ;;
*) args+=("-$opt") ;;
esac
done
doit() {
if ((verbose)) ; then echo "$*" 1>&2; fi
"$@"
}
shift $((OPTIND-1))
if [[ -n "$*" ]] ; then
nodes=("$@")
else
readarray -t nodes <<< "$(doit oc get node --no-headers -l "$label" | awk '{print $1}')"
fi
for node in "${nodes[@]}" ; do
# shellcheck disable=SC2086
# shellcheck disable=SC2029
if ((verbose)) ; then echo 1>&2; fi
disk=$(doit ssh -o StrictHostKeyChecking=no "core@$node" "cat > /tmp/diskfinder && chmod +x /tmp/diskfinder && /tmp/diskfinder ${args[*]}" <<< "$script")
if [[ -n "$disk" ]] ; then
printf "%s %s\n" "$node" "$disk"
else
echo "Can't find usable disk on $node" 1>&2
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment