ZVOL Blocksize Modifier

Resource ZVOL Blocksize Modifier

Fixed a typo in the logic for confirming the deletion of the original VDEV. This typo caused the script to exit before prompting the user with the question, but data was successfully copied.


Code:
#!/bin/bash

# Add this near the beginning of your script:
declare -i clean_exit=0

# Define color functions
color_output() {
    local color=$1
    local text=$2
    case $color in
        red) echo -e "\033[31m$text\033[0m" ;;
        green) echo -e "\033[32m$text\033[0m" ;;
        yellow) echo -e "\033[33m$text\033[0m" ;;
        blue) echo -e "\033[34m$text\033[0m" ;;
        purple) echo -e "\033[35m$text\033[0m" ;;
        cyan) echo -e "\033[36m$text\033[0m" ;;
        *) echo "$text" ;;
    esac
}

color_error() {
    echo "$(color_output red "$1")"
}

color_prompt() {
    echo "$(color_output blue "$1")"
}

bytes_to_human_readable() {
    local -i bytes=$1
    if ((bytes > 1099511627776)); then echo $((bytes / 1099511627776))"T"
    elif ((bytes > 1073741824)); then echo $((bytes / 1073741824))"G"
    elif ((bytes > 1048576)); then echo $((bytes / 1048576))"M"
    elif ((bytes > 1024)); then echo $((bytes / 1024))"K"
    else echo $bytes"B"
    fi
}
cleanup() {
    if (( clean_exit == 0 )); then
        color_error "\nExiting early. Performing cleanup..."
        if [[ -n "$NEW_ZVOL_NAME" ]]; then
            zfs destroy "$NEW_ZVOL_NAME" 2>/dev/null
        fi
    fi
}
trap cleanup SIGINT SIGTERM SIGHUP

tmp_error_log=$(mktemp)

# Intro message
echo "$(color_output yellow 'ZVOL Block Size Modifier v.03 - NickF')"
echo "$(color_output cyan '- Larger volblocksize can help with mostly sequential workloads and will gain a compression efficiency')"
echo "$(color_output green '- Smaller volblocksize can help with random workloads and minimize IO amplification, but will use more metadata and may have worse space efficiency')"
echo "$(color_output red 'This program is free software under the terms of the GNU General Public License. It is provided WITHOUT WARRANTY. See <https://www.gnu.org/licenses/> for more details.')"

read -p "Accept terms and proceed? [Y/N]: " choice
echo "[DEBUG] User choice for accepting terms: $choice"
if [[ "$choice" != [Yy] ]]; then
    exit 1
fi

# Generate a list of existing ZVOLs
zvols=($(zfs list -t volume -o name))

# Display the ZVOLs with aligned properties
echo -e "\nAvailable ZVOLs:"
for i in "${!zvols[@]}"; do
    if zfs list "${zvols[$i]}" &> /dev/null; then
        block_size=$(zfs get -H -o value volblocksize "${zvols[$i]}")
        provisioned_size=$(zfs get -H -o value volsize "${zvols[$i]}")
        used_space=$(zfs get -H -o value used "${zvols[$i]}")
        printf "[%d] %-50s Block Size: %-10s Used: %-10s Provisioned: %s\n" "$i" "${zvols[$i]}" "$block_size" "$used_space" "$provisioned_size"
    fi
done

# Get user's choice and validate
read -p "Choose the zvol number you'd like to clone: " index
echo "[DEBUG] User selected zvol number: $index"
if [[ -z ${zvols[$index]} ]]; then
    color_error "Invalid zvol number."
    rm "$tmp_error_log"
    exit 1
fi
OLD_ZVOL_NAME=${zvols[$index]}

# Get the desired block size and validate
read -p "Enter the new desired volblocksize (e.g. 8K, 16K, 32K): " NEW_BLOCK_SIZE
echo "[DEBUG] User provided block size: $NEW_BLOCK_SIZE"
if ! [[ $NEW_BLOCK_SIZE =~ ^[1-9][0-9]*[KkMmGgTt]$ ]]; then
    color_error "Invalid block size format."
    rm "$tmp_error_log"
    exit 1
fi

NEW_ZVOL_NAME="${OLD_ZVOL_NAME}-${NEW_BLOCK_SIZE}"
OLD_ZVOL_SIZE=$(zfs get -H -o value volsize "$OLD_ZVOL_NAME")

# Convert OLD_ZVOL_SIZE to bytes for the calculation
old_zvol_size_in_bytes=$(echo "$OLD_ZVOL_SIZE" | awk '/K/ {print $1*1024}
                                                     /M/ {print $1*1024^2}
                                                     /G/ {print $1*1024^3}
                                                     /T/ {print $1*1024^4}')

new_zvol_size_in_bytes=$(echo "$old_zvol_size_in_bytes" | awk '{print int($1 * 1.2)}')

# Convert back to a human-readable size for zfs create (assuming GiB for simplicity)
new_zvol_size_in_gib=$(( new_zvol_size_in_bytes / (1024**3) ))
echo "[DEBUG] Attempting to create a new zvol with name: $NEW_ZVOL_NAME, block size: $NEW_BLOCK_SIZE, and size: ${new_zvol_size_in_gib}G."
echo "INFO: New zvol will be created with a size 20% larger than the old one."


zfs create -s -V "${new_zvol_size_in_gib}G" -b "$NEW_BLOCK_SIZE" "$NEW_ZVOL_NAME" 2>"$tmp_error_log"
if [ $? -ne 0 ]; then
    color_error "Failed to create the new zvol. Here's the error:"
    cat "$tmp_error_log"
    rm "$tmp_error_log"
    exit 1
fi

# Check the used space of the new ZVOL
NEW_ZVOL_USED_SPACE=$(zfs get -H -o value used "$NEW_ZVOL_NAME")

# Convert the used space to bytes for comparison
NEW_ZVOL_USED_BYTES=$(echo "$NEW_ZVOL_USED_SPACE" | awk '/K/ {print $1*1024}
                                                         /M/ {print $1*1024^2}
                                                         /G/ {print $1*1024^3}
                                                         /T/ {print $1*1024^4}')


echo "INFO: Successfully created the new zvol: $NEW_ZVOL_NAME with the size: ${new_zvol_size_in_gib}G and block size: $NEW_BLOCK_SIZE."

# After new ZVOL creation
zpool_name=$(echo "$NEW_ZVOL_NAME" | cut -d'/' -f1)
pool_status=$(zpool status -x "$zpool_name" | head -1)
if [[ "$pool_status" != *"is healthy" ]]; then
    color_error "The zpool $zpool_name is in an unstable state: $pool_status. Exiting."
    rm "$tmp_error_log"
    exit 1
fi

# Function to get the available space on the pool
get_pool_avail_space_in_bytes() {
    local pool_name="$1"
    local avail_space
    avail_space=$(zfs list -H -o avail -r "$pool_name" | head -n 1)

    echo "$avail_space" | awk '/K/ {print $1*1024}
                                /M/ {print $1*1024^2}
                                /G/ {print $1*1024^3}
                                /T/ {print $1*1024^4}'
}

# Function to get the size of the ZVOL in bytes
get_zvol_size_in_bytes() {
    local zvol_name="$1"
    local zvol_size
    zvol_size=$(zfs get -H -o value used "$zvol_name")

    echo "$zvol_size" | awk '/K/ {print $1*1024}
                             /M/ {print $1*1024^2}
                             /G/ {print $1*1024^3}
                             /T/ {print $1*1024^4}'
}

# Identify the pool name from ZVOL path. Assuming ZVOL is in format: pool_name/...
POOL_NAME=$(echo "$OLD_ZVOL_NAME" | cut -d'/' -f1)

# Check if there's enough space in the pool for the data transfer
available_space_in_bytes=$(get_pool_avail_space_in_bytes "$POOL_NAME")
old_zvol_size_in_bytes=$(echo "$OLD_ZVOL_SIZE" | awk '/K/ {print $1*1024}
                                                     /M/ {print $1*1024^2}
                                                     /G/ {print $1*1024^3}
                                                     /T/ {print $1*1024^4}')

if (( available_space_in_bytes < old_zvol_size_in_bytes )); then
    color_error "Not enough space in the zpool to proceed with the data transfer. Exiting."
    zfs destroy "$NEW_ZVOL_NAME"
    exit 1
fi
echo "INFO: old_zvol_size_in_bytes = $old_zvol_size_in_bytes"
echo "INFO: available_space_in_bytes = $available_space_in_bytes"

# If there's enough space, proceed with the data transfer
echo "$(color_output yellow "Starting data transfer from the old zvol to the new one. Depending on the ZVOL size, this process can take a while. Please be patient and don't interrupt the operation.")"
dd if=/dev/zvol/"$OLD_ZVOL_NAME" of=/dev/zvol/"$NEW_ZVOL_NAME" bs="$NEW_BLOCK_SIZE" status=progress 2> errorlog.txt
if [ $? -ne 0 ]; then
    color_error "Data transfer failed. Here's the error:"
    cat errorlog.txt
    rm errorlog.txt
    zfs destroy "$NEW_ZVOL_NAME"
    exit 1
else
    rm errorlog.txt  # Removing errorlog.txt even if dd operation is successful
fi

# Check the used space of the new ZVOL
NEW_ZVOL_USED_SPACE=$(zfs get -H -o value used "$NEW_ZVOL_NAME")

MINIMUM_THRESHOLD=57344  # 56K in bytes
if [ "$NEW_ZVOL_USED_BYTES" -lt "$MINIMUM_THRESHOLD" ]; then
    color_error "Data transfer might not have been successful. Used space on new ZVOL is suspiciously low."
    exit 1
fi

# Confirmation for deletion
echo "Do you want to delete the original zvol?"
color_error "WARNING: This will permanently delete the original zvol!"
read -p "Delete $OLD_ZVOL_NAME? [y/N]: " choice

if [[ "$choice" == [Yy] ]]; then
    zfs destroy "$OLD_ZVOL_NAME"
    echo "$(color_output green 'Original zvol deleted successfully!')"
else
    echo "$(color_output yellow 'Original zvol retained.')"
fi
# Restart the service if needed
echo "$(color_output cyan 'Restarting the middlwared service can momentarily disrupt services relying on it. However, it might be necessary for changes to take effect in certain systems.')"
read -p "Do you wish to restart the middlwared service now? [y/N]: " restart_choice

if [[ "$restart_choice" == [Yy] ]]; then
    service middlwared restart
    echo "$(color_output green 'Middlwared service restarted successfully.')"
fi

# Summarize the actions
echo "$(color_output green 'SUMMARY:')"
echo "Original ZVOL: $OLD_ZVOL_NAME"
echo "New ZVOL: $NEW_ZVOL_NAME"
echo "Block Size Modified From: $(zfs get -H -o value volblocksize "$OLD_ZVOL_NAME") to: $NEW_BLOCK_SIZE"
echo "Data Successfully Transferred: Yes"

if [[ "$delete_choice" == [Yy] ]]; then
    echo "Original ZVOL Deleted: Yes"
else
    echo "Original ZVOL Deleted: No"
fi

# Set clean_exit to 1 right before the script's successful termination message:
echo "$(color_output green 'Operation completed successfully. Have a great day!')"
clean_exit=1
I've added some additional error handling and validation checking so as to prevent foot shooting.
Code:
#!/bin/bash

# Add this near the beginning of your script:
declare -i clean_exit=0

# Define color functions
color_output() {
    local color=$1
    local text=$2
    case $color in
        red) echo -e "\033[31m$text\033[0m" ;;
        green) echo -e "\033[32m$text\033[0m" ;;
        yellow) echo -e "\033[33m$text\033[0m" ;;
        blue) echo -e "\033[34m$text\033[0m" ;;
        purple) echo -e "\033[35m$text\033[0m" ;;
        cyan) echo -e "\033[36m$text\033[0m" ;;
        *) echo "$text" ;;
    esac
}

color_error() {
    echo "$(color_output red "$1")"
}

color_prompt() {
    echo "$(color_output blue "$1")"
}

bytes_to_human_readable() {
    local -i bytes=$1
    if ((bytes > 1099511627776)); then echo $((bytes / 1099511627776))"T"
    elif ((bytes > 1073741824)); then echo $((bytes / 1073741824))"G"
    elif ((bytes > 1048576)); then echo $((bytes / 1048576))"M"
    elif ((bytes > 1024)); then echo $((bytes / 1024))"K"
    else echo $bytes"B"
    fi
}

cleanup() {
    if (( clean_exit == 0 )); then
        color_error "\nExiting early. Performing cleanup..."
        if [[ -n "$NEW_ZVOL_NAME" ]]; then
            zfs destroy "$NEW_ZVOL_NAME" 2>/dev/null
        fi
    fi
}
trap cleanup SIGINT SIGTERM SIGHUP

tmp_error_log=$(mktemp)

# Intro message
echo "$(color_output yellow 'ZVOL Block Size Modifier v.02 - NickF')"
echo "$(color_output cyan '- Larger volblocksize can help with mostly sequential workloads and will gain a compression efficiency')"
echo "$(color_output green '- Smaller volblocksize can help with random workloads and minimize IO amplification, but will use more metadata and may have worse space efficiency')"
echo "$(color_output red 'This program is free software under the terms of the GNU General Public License. It is provided WITHOUT WARRANTY. See <https://www.gnu.org/licenses/> for more details.')"

read -p "Accept terms and proceed? [Y/N]: " choice
if [[ "$choice" != [Yy] ]]; then
    exit 1
fi

# Generate a list of existing ZVOLs
zvols=($(zfs list -t volume -o name))

# Display the ZVOLs with aligned properties
echo -e "\nAvailable ZVOLs:"
for i in "${!zvols[@]}"; do
    if zfs list "${zvols[$i]}" &> /dev/null; then
        block_size=$(zfs get -H -o value volblocksize "${zvols[$i]}")
        provisioned_size=$(zfs get -H -o value volsize "${zvols[$i]}")
        used_space=$(zfs get -H -o value used "${zvols[$i]}")
        printf "[%d] %-50s Block Size: %-10s Used: %-10s Provisioned: %s\n" "$i" "${zvols[$i]}" "$block_size" "$used_space" "$provisioned_size"
    fi
done

# Get user's choice and validate
read -p "Choose the zvol number you'd like to clone: " index
if [[ -z ${zvols[$index]} ]]; then
    color_error "Invalid zvol number."
    rm "$tmp_error_log"
    exit 1
fi
OLD_ZVOL_NAME=${zvols[$index]}

# Get the desired block size and validate
read -p "Enter the new desired volblocksize (e.g. 8K, 16K, 32K): " NEW_BLOCK_SIZE
if ! [[ $NEW_BLOCK_SIZE =~ ^[1-9][0-9]*[KkMmGgTt]$ ]]; then
    color_error "Invalid block size format."
    rm "$tmp_error_log"
    exit 1
fi

NEW_ZVOL_NAME="${OLD_ZVOL_NAME}-${NEW_BLOCK_SIZE}"
OLD_ZVOL_SIZE=$(zfs get -H -o value volsize "$OLD_ZVOL_NAME")

# Convert OLD_ZVOL_SIZE to bytes for the calculation
old_zvol_size_in_bytes=$(echo "$OLD_ZVOL_SIZE" | awk '/K/ {print $1*1024} 
                                                     /M/ {print $1*1024^2}
                                                     /G/ {print $1*1024^3}
                                                     /T/ {print $1*1024^4}')

new_zvol_size_in_bytes=$(( old_zvol_size_in_bytes * 12 / 10 ))

# Convert back to a human-readable size for zfs create (assuming GiB for simplicity)
new_zvol_size_in_gib=$(( new_zvol_size_in_bytes / (1024**3) ))

echo "INFO: Old zvol size was: $OLD_ZVOL_SIZE."
echo "INFO: New zvol will be created with a size 20% larger than the old one."
echo "INFO: Calculated new zvol size: ${new_zvol_size_in_gib}G."

echo "Creating the new zvol named: $NEW_ZVOL_NAME with block size: $NEW_BLOCK_SIZE and size: ${new_zvol_size_in_gib}G."
zfs create -s -V "${new_zvol_size_in_gib}G" -b "$NEW_BLOCK_SIZE" "$NEW_ZVOL_NAME" 2>"$tmp_error_log"
if [ $? -ne 0 ]; then
    color_error "Failed to create the new zvol. Here's the error:"
    cat "$tmp_error_log"
    rm "$tmp_error_log"
    exit 1
fi

echo "INFO: Successfully created the new zvol: $NEW_ZVOL_NAME with the size: ${new_zvol_size_in_gib}G and block size: $NEW_BLOCK_SIZE."

# After new ZVOL creation
zpool_name=$(echo "$NEW_ZVOL_NAME" | cut -d'/' -f1)
pool_status=$(zpool status -x "$zpool_name" | head -1)
if [[ "$pool_status" != *"is healthy" ]]; then
    color_error "The zpool $zpool_name is in an unstable state: $pool_status. Exiting."
    rm "$tmp_error_log"
    exit 1
fi

# Function to get the available space on the pool
get_pool_avail_space_in_bytes() {
    local pool_name="$1"
    local avail_space
    avail_space=$(zfs list -H -o avail -r "$pool_name" | head -n 1)

    echo "$avail_space" | awk '/K/ {print $1*1024} 
                                /M/ {print $1*1024^2}
                                /G/ {print $1*1024^3}
                                /T/ {print $1*1024^4}'
}

# Function to get the size of the ZVOL in bytes
get_zvol_size_in_bytes() {
    local zvol_name="$1"
    local zvol_size
    zvol_size=$(zfs get -H -o value used "$zvol_name")

    echo "$zvol_size" | awk '/K/ {print $1*1024} 
                             /M/ {print $1*1024^2}
                             /G/ {print $1*1024^3}
                             /T/ {print $1*1024^4}'
}

# Identify the pool name from ZVOL path. Assuming ZVOL is in format: pool_name/...
POOL_NAME=$(echo "$OLD_ZVOL_NAME" | cut -d'/' -f1)

# Check if there's enough space in the pool for the data transfer
available_space_in_bytes=$(get_pool_avail_space_in_bytes "$POOL_NAME")
old_zvol_size_in_bytes=$(get_zvol_size_in_bytes "$OLD_ZVOL_NAME")

if (( available_space_in_bytes < old_zvol_size_in_bytes )); then
    color_error "Not enough space in the zpool to proceed with the data transfer. Exiting."
    zfs destroy "$NEW_ZVOL_NAME"
    exit 1
fi
echo "INFO: old_zvol_size_in_bytes = $old_zvol_size_in_bytes"
echo "INFO: available_space_in_bytes = $available_space_in_bytes"

# If there's enough space, proceed with the data transfer
echo "$(color_output yellow "Starting data transfer from the old zvol to the new one. Depending on the ZVOL size, this process can take a while. Please be patient and don't interrupt the operation.")"
dd if=/dev/zvol/"$OLD_ZVOL_NAME" of=/dev/zvol/"$NEW_ZVOL_NAME" bs="$NEW_BLOCK_SIZE" status=progress 2> errorlog.txt
if [ $? -ne 0 ]; then
    color_error "Data transfer failed. Here's the error:"
    cat errorlog.txt
    rm errorlog.txt
    zfs destroy "$NEW_ZVOL_NAME"
    exit 1
fi

# Check the used space of the new ZVOL
NEW_ZVOL_USED_SPACE=$(zfs get -H -o value used "$NEW_ZVOL_NAME")

# Convert the used space to bytes for comparison
NEW_ZVOL_USED_BYTES=$(echo "$NEW_ZVOL_USED_SPACE" | awk '/K/ {print $1*1024} 
                                                         /M/ {print $1*1024^2}
                                                         /G/ {print $1*1024^3}
                                                         /T/ {print $1*1024^4}')

MINIMUM_THRESHOLD=57344  # 56K in bytes
if [ "$NEW_ZVOL_USED_BYTES" -lt "$MINIMUM_THRESHOLD" ]; then
    color_error "Data transfer might not have been successful. Used space on new ZVOL is suspiciously low."
    exit 1
fi

# Confirmation for deletion
echo "$(color_output yellow 'You are about to delete the original ZVOL which means the data will only exist on the new ZVOL with the modified block size. Make sure you have backups or you're certain about this action.')"
read -p "Delete the original zvol ($OLD_ZVOL_NAME)? [y/N]: " delete_choice
if [[ "$delete_choice" == [Yy] ]]; then
    zfs destroy "$OLD_ZVOL_NAME"
    echo "Original zvol $OLD_ZVOL_NAME has been deleted."
else
    echo "$(color_output yellow 'Original ZVOL retained.')"
fi

# Restart the service if needed
echo "$(color_output cyan 'Restarting the middlwared service can momentarily disrupt services relying on it. However, it might be necessary for changes to take effect in certain systems.')"
read -p "Do you wish to restart the middlwared service now? [y/N]: " restart_choice
if [[ "$restart_choice" == [Yy] ]]; then
    service middlwared restart
    echo "$(color_output green 'Middlwared service restarted successfully.')"
fi

# Summarize the actions
echo "$(color_output green 'SUMMARY:')"
echo "Original ZVOL: $OLD_ZVOL_NAME"
echo "New ZVOL: $NEW_ZVOL_NAME"
echo "Block Size Modified From: $(zfs get -H -o value volblocksize "$OLD_ZVOL_NAME") to: $NEW_BLOCK_SIZE"
echo "Data Successfully Transferred: Yes"
if [[ "$delete_choice" == [Yy] ]]; then
    echo "Original ZVOL Deleted: Yes"
else
    echo "Original ZVOL Deleted: No"
fi

# Set clean_exit to 1 right before the script's successful termination message:
echo "$(color_output green 'Operation completed successfully. Have a great day!')"
clean_exit=1


Top