#! /bin/bash
# vim: shiftwidth=4 tabstop=4 expandtab

SCHEDULED_SUBCOMMANDS=( config list run cleanup check )

# shellcheck source=/dev/null
source "$LIB_DIR"/snapshot

# Auto-complete twik: allow sourcing this file from complete command to get supported args & alias
[[ ${IN_AUTOCOMPLETER:-0} -eq 1 ]] && return

subcommand__help() {
    if [[ -z "$2" ]] || [[ "$2" == "ls" ]]; then
        # shellcheck disable=SC2153
        echo "$COMMAND ls [VM] [-l|--full|--details]"
        if [[ "$1" == "details" ]] && [[ "$2" == "ls" ]]; then
            echo "  List all snapshots of a VM specified by name"
            echo "    [VM]                    VM name"
            echo "    [-l|--full|--details]   Show snapshot's volumes details"
            echo "    [-J|--json]             JSON output"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "info" ]]; then
        echo "$COMMAND info [VM] [snapshot]"
        if [[ "$1" == "details" ]] && [[ "$2" == "info" ]]; then
            echo "  Show info of a VM's snapshot specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "create" ]]; then
        echo "$COMMAND create [-s suffix] [-p prefix] [-P pattern] [VM]"
        if [[ "$1" == "details" ]] && [[ "$2" == "create" ]]; then
            echo "  Create a new VM snapshot"
            echo "    -s|--suffix [suffix]            The snapshot name suffix (default:" \
                "'$(default_snap_suffix)')"
            echo "    -p|--prefix [prefix]            The snapshot name prefix" \
                "(default: current date & time)"
            echo "    -P|--volumes-pattern [pattern]  Optional pattern to filter VM volumes" \
                "to snapshot"
            echo "    [VM]                            VM name"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "protect" ]]; then
        echo "$COMMAND protect [VM] [snapshot]"
        if [[ "$1" == "details" ]] && [[ "$2" == "protect" ]]; then
            echo "  Protect VM's snapshot specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "unprotect" ]]; then
        echo "$COMMAND unprotect [VM] [snapshot]"
        if [[ "$1" == "details" ]] && [[ "$2" == "unprotect" ]]; then
            echo "  Unprotect VM's snapshot specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "size" ]]; then
        echo "$COMMAND size [VM] [snapshot]"
        if [[ "$1" == "details" ]] && [[ "$2" == "size" ]]; then
            echo "  Show VM's snapshot size specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "rm" ]]; then
        echo "$COMMAND rm [VM] [snapshot] [wait]"
        if [[ "$1" == "details" ]] && [[ "$2" == "rm" ]]; then
            echo "  Remove a VM's snapshot specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
            echo "    [wait]              Waiting cluster is cleaned between each snapshot" \
                "removals"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "restore" ]]; then
        echo "$COMMAND restore [VM] [snapshot]"
        if [[ "$1" == "details" ]] && [[ "$2" == "restore" ]]; then
            echo "  Restore a VM's snapshot specified by name"
            echo "    [VM]                VM name"
            echo "    [snapshot]          Snapshot name"
            echo
            echo "  Note: This command will rename the current VM's volumes with a suffix computed"
            echo "  using date and the format '$RESTORE_OLD_VOLUMES_NAME_SUFFIX_FORMAT' before "
            echo "  creating new ones from the specified snapshot."
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "scheduled" ]]; then
        echo "$COMMAND scheduled [config | list [pool/image] | run [-D X | VM] | cleanup" \
            "[ALL|pool/image] | check]"
        if [[ "$1" == "details" ]] && [[ "$2" == "scheduled" ]]; then
            echo "  Manage scheduled snapshots:"
            echo "    config              Show scheduled snapshots configuration"
            echo "    list                List existing scheduled snapshots"
            echo "    list [pool/image]   List existing scheduled snapshots of the specified volume"
            echo "    run                 Run scheduled snapshot of the VMs of the day"
            echo "    run -D/--weekday X  Run scheduled snapshots as if it were the specified day"
            echo "                        Note: Week-day is specified by ID (monday=1,... sunday=7)"
            echo "    run VM1 VM2 ...     Run scheduled snapshots for specified VMs"
            echo "    cleanup ALL         Cleanup all old VMs's scheduled snapshots"
            echo "    cleanup pool/image  Cleanup specified volume's old scheduled snapshots"
            echo "                        Note: you could specify multiple volumes RBD path"
            echo "    check               Check configuration and VMs's existing scheduled"
            echo "                        snapshots count & age. Thresholds could be specified in"
            echo "                        configuration or using the following arguments:"
            echo "                          -W / --warning-age     Warning threshold of the latest snap"
            echo "                          -C / --critcial-age    Critical threshold of the latest snap"
            echo "                          -w / --warning-count   Warning threshold of snaps count"
            echo "                          -c / --critcial-count  Critical threshold of snaps count"
            echo
            echo "  Other parameters accepted by all of this subcommands:"
            echo "    -f|--format         Set custom snapshot name format"
            echo "                        (default: '$SCHEDULED_SNAPSHOTS_NAME_FORMAT')"
            echo "    -p|--pattern        Set custom snapshot name pattern"
            echo "                        (default: '${SCHEDULED_SNAPSHOTS_NAME_PATTERN:-(based on name format)}')"
        fi
    fi
}

subcommand__ls() {
    check_generate_script_not_supported
    local vm="" details=0 json=0 opt hyp
    for opt in "${@:1}"; do
        case $opt in
            -l|--full|--details)
                details=1
                ;;
            -J|--json)
                json=1
                ;;
            *)
                if [[ -z "$vm" ]]; then
                    vm=$opt
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
    done
    [[ -z "$vm" ]] && usage "VM name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    # List VM snapshots
    local -A snaps snaps_data
    local -a snaps_order
    local vol snap_data snap
    for vol in $(list_volumes "$vm" "$hyp"); do
        if [[ $details -eq 1 ]]; then
            get_snapshots_info "$vol" VOL_SNAPS_DATA with-size
        else
            get_snapshots_info "$vol" VOL_SNAPS_DATA
        fi
        log DEBUG "Volume $vol snapshots info: $VOL_SNAPS_DATA"
        set_newline_ifs
        for snap_data in $(jq -rc '.[]' <<< "$VOL_SNAPS_DATA"); do
            snap=$( jq -r '.name' <<< "$snap_data" )
            in_array "$snap" "${snaps_order[@]}" || snaps_order+=( "$snap" )
            if [[ -z "${snaps["$snap"]}" ]]; then
                snaps+=( ["$snap"]="$vol" )
            else
                snaps["$snap"]="${snaps["${snap}"]} $vol"
            fi
            snaps_data+=( ["$vol@$snap"]="$snap_data" )
        done
        restore_ifs
    done

    [[ ${#snaps[@]} -eq 0 ]] && [[ $json -eq 0 ]] && echo "VM $vm have no snapshot." && exit 0

    if [[ $details -ne 1 ]]; then
        if [[ $json -eq 1 ]]; then
            if [[ ${#snaps[@]} -eq 0 ]]; then
                echo '[]'
            else
                jq -R 'split(" ")' <<< "${snaps_order[@]}"
            fi
        else
            echo -e "$(implode "\n" "${snaps_order[@]}")"
        fi
        exit 0
    fi

    local rows row
    [[ $json -eq 0 ]] && rows='[]' || rows='{}'
    local total_used_size=0 size used_size
    for snap in "${snaps_order[@]}"; do
        local first=1 snap_total_size=0 snap_total_used_size=0
        [[ $json -eq 1 ]] && rows=$(jq -r ". + {\"$snap\": {\"volumes\": []}}" <<< "$rows")
        for vol in ${snaps["${snap}"]}; do
            if [[ $first -eq 1 ]] || [[ $json -eq 1 ]]; then
                local snap_col=$snap
                first=0
            else
                local snap_col="-"
            fi
            size=$( jq -r '.size' <<< "${snaps_data["$vol@$snap"]}" )
            (( snap_total_size+=size ))
            used_size=$( jq -r '.used_size' <<< "${snaps_data["$vol@$snap"]}" )
            (( snap_total_used_size+=used_size ))
            row=$(
                jq -r \
                    --arg snap "$snap_col" \
                    --arg vol "$vol" \
                    --arg size "$( format_size -b -z "$size" )" \
                    --arg used_size "$( format_size -b -z "$used_size" )" \
                    '. | {
                        "snapshot": $snap,
                        "volume": $vol,
                        "snapshot ID": .id,
                        "volume size": $size,
                        "used size": $used_size,
                        "date": .timestamp,
                        "protected": .protected
                    }' <<< "${snaps_data["$vol@$snap"]}"
            )
            log DEBUG "Snapshot $vol@$snap row: $row"
            if [[ $json -eq 0 ]]; then
                rows=$( jq ". + [$row]" <<< "$rows" )
            else
                rows=$( jq ".[\"$snap\"].volumes += [$row]" <<< "$rows" )
            fi
        done

        if [[ $json -eq 0 ]]; then
            # Add row with totals
            row=$(
                jo \
                    snapshot="" volume="" "snapshot ID=Total:" "date=" "protected=" \
                    "volume size=$(format_size -z -b $snap_total_size)" \
                    "used size=$(format_size -z -b $snap_total_used_size)"
            )
            # Add an empty row
            rows=$( jq ". + [$row]" <<< "$rows" )
            row=$(
                jo snapshot="-" volume="" "snapshot ID=" "date=" "protected=" \
                "volume size=" "used size="
            )
            rows=$( jq ". + [$row]" <<< "$rows" )
        else
            rows=$(
                jq -r "
                    .[\"$snap\"].total_size=\"$(format_size -z -b $snap_total_size)\" |
                    .[\"$snap\"].total_used_size=\"$(format_size -z -b $snap_total_used_size)\"
                " <<< "$rows"
            )
        fi
        (( total_used_size+=snap_total_used_size ))
    done

    # Add row with VM totals
    if [[ $json -eq 0 ]]; then
        row=$(
            jo \
                snapshot="" volume="" "snapshot ID=" "date=" "protected=" \
                "volume size=Total size:" "used size=$(format_size -z -b $total_used_size)"
        )
        rows=$( jq ". + [$row]" <<< "$rows" )
    else
        rows=$( jq ".total_used_size=\"$(format_size -z -b $total_used_size)\"" <<< "$rows" )
    fi
    if [[ $json -eq 1 ]]; then
        jq <<< "$rows"
    else
        log DEBUG "Rows: $rows"
        print_json_table \
            snapshot volume "snapshot ID" "volume size" "used size" "date" "protected" <<< "$rows"
    fi
}

subcommand__info() {
    check_generate_script_not_supported
    local vm=$1 hyp
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=${2}
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"
    local volumes=() size total_size=0 used_size total_used_size=0 vol_row vol_rows='[]' vol snap
    for vol in $(list_volumes "$vm" "$hyp"); do
        get_snapshots_info "$vol" SNAPS_DATA with-size
        set_newline_ifs
        # shellcheck disable=SC2153
        for snap_data in $(jq -rc '.[]' <<< "$SNAPS_DATA"); do
            snap=$( jq -r '.name' <<< "$snap_data" )
            [[ "$snap" != "$snap_name" ]] && continue
            size=$( jq -r '.size' <<< "$snap_data" )
            (( total_size+=size ))
            used_size=$( jq -r '.used_size' <<< "$snap_data" )
            (( total_used_size+=used_size ))
            vol_row=$(
                jq -r \
                    --arg vol "$vol" \
                    --arg size "$( format_size -b -z "$size" )" \
                    --arg used_size "$( format_size -b -z "$used_size" )" \
                    '. | {
                        "volume": $vol,
                        "snapshot ID": .id,
                        "volume size": $size,
                        "used size": $used_size,
                        "date": .timestamp,
                        "protected": .protected
                    }' <<< "$snap_data"
            )
            vol_rows=$( jq ". + [$vol_row]" <<< "$vol_rows" )
            volumes+=( "$vol" )
            break
        done
        restore_ifs
    done
    [[ ${#volumes[@]} -eq 0 ]] && usage "Unknown VM $vm snapshot $snap_name"

    # Add row with totals
    vol_row=$(
        jo \
            volume="" "snapshot ID=Total:" "date=" "protected=" \
            "volume size=$(format_size -z -b $total_size)" \
            "used size=$(format_size -z -b $total_used_size)"
    )
    vol_rows=$( jq ". + [$vol_row]" <<< "$vol_rows" )

    echo "VM $vm snapshot $snap_name:"
    echo
    print_json_table volume "snapshot ID" "volume size" "used size" "date" "protected" <<< "$vol_rows"
}

subcommand__create() {
    local vm idx=1 snap_suffix snap_prefix hyp volumes_pattern
    while [[ $idx -le $# ]]; do
        local opt=${!idx}
        case $opt in
            -p|--prefix)
                ((idx++))
                snap_prefix=${!idx}
                ;;
            -s|--suffix)
                ((idx++))
                snap_suffix=${!idx}
                ;;
            -P|--volumes-pattern)
                ((idx++))
                volumes_pattern=${!idx}
                ;;
            *)
                [[ -n "$vm" ]] && usage "Invalid parameter '$opt'"
                vm="$opt"
                ;;
        esac
        ((idx++))
    done
    [[ -z "$vm" ]] && usage "VM name is missing"
    [[ -z "${snap_prefix:-}" ]] && snap_prefix=$(date +%Y%m%d-%H%M%S)
    [[ -z "${snap_suffix:-}" ]] && snap_suffix=$(default_snap_suffix)

    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    local snap_name="${snap_prefix}-${snap_suffix}"
    check_snapshot "$vm" "$hyp" "$snap_name" && fatal_error \
      "A snapshot ${snap_name} already exist for VM $vm"

    create_snapshot "$vm" "$hyp" "$snap_name" "$volumes_pattern"
    exit $?
}

subcommand__protect() {
    local vm=$1 hyp
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=$2
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    local volume snap_data protected
    for volume in $(list_volumes "$vm" "$hyp"); do
        get_snapshots_info "$volume" SNAPS_DATA
        snap_data=$( jq -r ".[] | select(.name == \"$snap_name\")" <<< "$SNAPS_DATA" )
        [[ -z "$snap_data" ]] && \
            display "No snapshot '$snap_name' found for the volume $volume" && continue
        protected=$( jq -r '.protected' <<< "$snap_data" )
        [[ "$protected" == "true" ]] &&
            display "$volume@$snap_name is already protected" && \
            continue
        display "Protect $volume@$snap_name..."
        run_external rbd snap protect "$volume@$snap_name" || \
            log ERROR "Failed to protect $volume@$snap_name"
    done
}

subcommand__unprotect() {
    local vm=$1 hyp
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=${2}
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    local volume snap_data protected
    for volume in $(list_volumes "$vm" "$hyp"); do
        get_snapshots_info "$volume" SNAPS_DATA
        snap_data=$( jq -r ".[] | select(.name == \"$snap_name\")" <<< "$SNAPS_DATA" )
        [[ -z "$snap_data" ]] && \
            display "No snapshot '$snap_name' found for the volume $volume" && continue
        protected=$( jq -r '.protected' <<< "$snap_data" )
        [[ "$protected" == "false" ]] &&
            display "$volume@$snap_name is already unprotected" && \
            continue
        display "Unprotect $volume@$snap_name..."
        run_external rbd snap unprotect "$volume@$snap_name" || \
            log ERROR "Failed to unprotect $volume@$snap_name"
    done
}

subcommand__size() {
    check_generate_script_not_supported
    local vm=$1 hyp
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=${2}
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    local volumes=() size total_size=0 volume snap_data
    for volume in $(list_volumes "$vm" "$hyp"); do
        get_snapshots_info "$volume" SNAPS_DATA with-size
        snap_data=$( jq -r ".[] | select(.name == \"$snap_name\")" <<< "$SNAPS_DATA" )
        [[ -z "$snap_data" ]] && continue
        size=$( jq -r '.used_size' <<< "$snap_data" )
        (( total_size+=size ))
        volumes+=( "$volume" )
        echo "Snapshot ${volume}@${snap_name} size is $(format_size -z -b "$size")"
    done
    [[ ${#volumes[@]} -eq 0 ]] && usage "Unknown snapshot '$snap_name' for VM $vm"
    echo "Total size: $(format_size -z -b $total_size)"
}

subcommand__rm() {
    local vm=$1
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=${2}
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    wait=0
    [[ "$3" == "wait" ]] && wait=1
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"

    local -a snaps
    local snaps_size=0 size snap_data
    for volume in $(list_volumes "$vm" "$hyp"); do
        get_snapshots_info "$volume" SNAPS_DATA with-size
        snap_data=$( jq -r ".[] | select(.name == \"$snap_name\")" <<< "$SNAPS_DATA" )
        [[ -z "$snap_data" ]] && continue
        # shellcheck disable=SC2190
        snaps+=( "$volume@$snap_name" )
        size=$( jq -r '.used_size' <<< "$snap_data" )
        debug "Snapshot $volume@$snap_name size is $(format_size -z -b "$size")"
        (( snaps_size+=size ))
    done

    [[ ${#snaps[@]} -eq 0 ]] && usage "Unknown snapshot '$snap_name' for VM $vm"

    confirm "Remove the snapshot $snap_name of the VM $vm" \
        "(size: $(format_size -z -b $snaps_size))" || exit 1

    # Check working hours & size limit
    local snaps_size_limit
    snaps_size_limit=$(( WORKING_HOURS_SNAPSHOT_REMOVAL_SIZE_LIMIT*1073741824 ))
    if in_working_hours && [[ $snaps_size -ge $snaps_size_limit ]]; then
        [[ $AUTO -eq 1 ]] && [[ $FORCE -ne 1 ]] && \
            fatal_error "Auto mode enabled without force mode, deny deleting snapshot during" \
            "working hours if their size is greater than" \
            "${WORKING_HOURS_SNAPSHOT_REMOVAL_SIZE_LIMIT}GB. Run again this command with" \
            "-F/--force parameter to force it."
        echo
        echo "/!\\   WARNING   /!\\"
        echo "Deleting a snapshot imposes a significant load on the Ceph cluster. It is highly"
        echo "recommended not to perform this type of operation on large snapshot during the day!"
        echo
        confirm || exit 1
    fi

    [[ $wait -eq 1 ]] && display "Check cluster is not currently removing another snapshot" && \
        wait_until_ceph_cluster_not_snaptrims_and_health_ok
    local snap start_time duration
    for snap in "${snaps[@]}"; do
        start_time=$(date +%s)
        display "Remove ${snap}..."
        run_external rbd snap unprotect "${snap}"
        run_external rbd snap rm "${snap}" || fatal_error "Failed to remove snapshot ${snap}!"
        [[ $wait -eq 1 ]] && wait_until_ceph_cluster_not_snaptrims_and_health_ok 1
        (( duration=$(date +%s)-start_time ))
        display "done (in $(format_duration $duration))."
    done
}

subcommand__restore() {
    local vm=$1 hyp volumes vol old_suffix
    [[ -z "$vm" ]] && usage "VM name is missing"
    local snap_name=${2}
    [[ -z "$snap_name" ]] && usage "Snapshot name is missing"
    hyp=$( locate "$vm" )
    check_hyp "$hyp" || usage "Unknown VM $vm"
    check_snapshot "$vm" "$hyp" "$snap_name" || \
        usage "Unknown snapshot '$snap_name' for VM $vm"

    confirm "Do you want to restore VM $vm from snapshot $snap_name" || exit 1

    mapfile -t volumes < <( list_volumes "$vm" "$hyp" )
    for vol in "${volumes[@]}"; do
        run_external rbd snap protect "${vol}@${snap_name}"
        run_external rbd clone "${vol}@${snap_name}" "${vol}_TEMP_RESTORE" || \
            fatal_error "Failed to clone snapshot ${vol}@${snap_name} as ${vol}_TEMP_RESTORE!"
    done

    is_running "$vm" "$hyp" && stop_and_wait_stopped "$vm" "$hyp"

    old_suffix=$( date "+$RESTORE_OLD_VOLUMES_NAME_SUFFIX_FORMAT" )
    for vol in "${volumes[@]}"; do
        run_external rbd mv "${vol}" "${vol}_${old_suffix}" || \
            fatal_error "Failed to rename ${vol} as ${vol}_${old_suffix} !"
        run_external rbd mv "${vol}_TEMP_RESTORE" "${vol}" || \
            fatal_error "Failed to rename ${vol}_TEMP_RESTORE as ${vol} !"
    done
    run_external ssh "$hyp" "$REAL_VIRSH" start "$vm" || \
        error "Failed to restart VM $vm."

    if [[ -n "$GENERATE_SCRIPT" ]]; then
        {
            echo "echo 'If everything is OK, you should now do some cleanup.'"
            echo "echo '/!\\ Beware that flatten and rm can be IO intensive operations.'"
            echo "echo -n 'Press [enter] to continue'"
            echo "read -r"
        } >> "$GENERATE_SCRIPT"
        for vol in "${volumes[@]}"; do
            {
                echo "rbd flatten ${vol}"
                echo "rbd snap unprotect ${vol}_${old_suffix}@${snap_name}"
                echo "rbd snap rm ${vol}_${old_suffix}@${snap_name}"
                echo "rbd rm ${vol}_${old_suffix}"
                echo
            } >> "$GENERATE_SCRIPT"
        done
        return 0
    fi
    display "# If everything is OK, you should now do some cleanup."
    display "# Beware that flatten and rm can be IO intensive operations...\n"
    for vol in "${volumes[@]}"; do
        display " rbd flatten ${vol}"
        display " rbd snap unprotect ${vol}_${old_suffix}@${snap_name}"
        display " rbd snap rm ${vol}_${old_suffix}@${snap_name}"
        display " rbd rm ${vol}_${old_suffix}"
    done
}

subcommand__scheduled() {
    local command="" args=() idx=1
    while [[ $idx -le $# ]]; do
        local opt=${!idx}
        case $opt in
            -f|--format)
                ((idx++))
                SCHEDULED_SNAPSHOTS_NAME_FORMAT=${!idx}
                ;;
            -p|--pattern)
                ((idx++))
                SCHEDULED_SNAPSHOTS_NAME_PATTERN=${!idx}
                ;;
            *)
                if [[ -n "$command" ]]; then
                    args+=( "$opt" )
                elif in_array "$opt" "${SCHEDULED_SUBCOMMANDS[@]}"; then
                    command=$opt
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
        ((idx++))
    done

    [[ -z "$command" ]] && usage "You must provide snapshot scheduled subcommand" \
        "($( implode ', ' "${SCHEDULED_SUBCOMMANDS[@]}" ))"

    subcommand__scheduled__"$command" "${args[@]}"
}

subcommand__scheduled__config() {
    local vm volume day
    check_generate_script_not_supported
    display "VIP: ${VIP:-UNCONFIGURED}"
    display
    display "Scheduled snapshots name format: '$SCHEDULED_SNAPSHOTS_NAME_FORMAT'"
    display "  Example: '$( date "+$SCHEDULED_SNAPSHOTS_NAME_FORMAT" )'"
    if [[ -n "$SCHEDULED_SNAPSHOTS_NAME_PATTERN" ]]; then
        display "Scheduled snapshots name pattern: '$SCHEDULED_SNAPSHOTS_NAME_PATTERN'"
        check_scheduled_snapshots_name_pattern || \
            display "  /!\ WARNING /!\ Configured name pattern does not match with configured" \
                "name format."
    else
        display "Scheduled snapshots name pattern: unconfigured"
        generate_scheduled_snapshots_name_pattern
        display "  Generated pattern: '$SCHEDULED_SNAPSHOTS_NAME_PATTERN'"
        check_scheduled_snapshots_name_pattern || \
            display "  /!\ WARNING /!\ Generated name pattern does not match with configured" \
                "name format."
    fi
    display
    display "Retention:"
    display "  Default: $SCHEDULED_SNAPSHOTS_RETENTION snapshot(s)"
    if [[ ${#SCHEDULED_SNAPSHOTS_VMS_RETENTION[@]} -eq 0 ]]; then
        display "  No custom VM retention configured."
    else
        display "  Custom VM retention:"
        for vm in $( implode $'\n' "${!SCHEDULED_SNAPSHOTS_VMS_RETENTION[@]}" | sort ); do
            display "  - $vm: ${SCHEDULED_SNAPSHOTS_VMS_RETENTION[$vm]} snapshot(s)"
            check_int "${SCHEDULED_SNAPSHOTS_VMS_RETENTION[$vm]}" 0 || \
                display "    /!\ Invalid retention value configured for VM $vm" \
                    "(${SCHEDULED_SNAPSHOTS_VMS_RETENTION[$vm]}), must be a positive integer."
        done
    fi
    if [[ ${#SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[@]} -eq 0 ]]; then
        display "  No custom shared volume retention configured."
    else
        display "  Custom shared volume retention:"
        for volume in "${!SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[@]}"; do
            display "  - $volume: ${SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[$volume]}" \
                "snapshot(s)"
            check_int "${SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[$volume]}" 0 || \
                display "    /!\ Invalid retention value configured for shared volume $volume" \
                    "(${SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[$volume]}), must be a" \
                    "positive integer."
        done
    fi
    display
    display "VMs list:"
    get_scheduled_snapshots_vms_list
    for vm in "${SCHEDULED_SNAPSHOTS_VMS_LIST[@]}"; do
        display "  - $vm"
    done
    [[ ${#SCHEDULED_SNAPSHOTS_VMS_LIST[@]} -eq 0 ]] && display "  No included VM"
    display
    display "VMs volumes pattern:"
    for vm in "${!SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[@]}"; do
        display "  - $vm: '${SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[$vm]}'"
    done
    [[ ${#SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[@]} -eq 0 ]] && \
        display "  No configured VM volumes pattern"
    display
    display "Shared volumes:"
    load_volume_to_vm_mapping
    local shared_volumes_with_scheduled_snapshots=0
    for volume in "${!SHARED_VOLUMES[@]}"; do
        for vm in ${SHARED_VOLUMES[$volume]}; do
            in_array "$vm" "${SCHEDULED_SNAPSHOTS_VMS_LIST[@]}" || continue
            display "  - $volume: shared by ${SHARED_VOLUMES[$volume]}"
            (( shared_volumes_with_scheduled_snapshots+=1 ))
            break
        done
    done
    [[ $shared_volumes_with_scheduled_snapshots -eq 0 ]] && display "  No shared volume"
    display
    display "Planning:"
    display "  Wait delay between each scheduled snapshots: ${SCHEDULED_SNAPSHOTS_WAIT_DELAY}s"
    SCHEDULED_SNAPSHOTS_DAYS_NAMES=()
    for day in "${SCHEDULED_SNAPSHOTS_DAYS[@]}"; do
        SCHEDULED_SNAPSHOTS_DAYS_NAMES+=( "$(format_weekday "$day")" )
    done
    display "  Days when scheduled snapshots are done: ${SCHEDULED_SNAPSHOTS_DAYS_NAMES[*]}"

    if [[ ${#SCHEDULED_SNAPSHOTS_VMS_DAY[@]} -eq 0 ]]; then
        display "  No custom VM scheduled snapshots weekday configured"
    else
        display "  Custom VM scheduled snapshots weekday:"
        for vm in "${!SCHEDULED_SNAPSHOTS_VMS_DAY[@]}"; do
            display "  - $vm: on $( format_weekday "${SCHEDULED_SNAPSHOTS_VMS_DAY[$vm]}" )"
        done
    fi
    get_scheduled_snapshots_planning VMS_BY_DAY
    display "  Computed planning for all VMs:"
    for day in "${SCHEDULED_SNAPSHOTS_DAYS[@]}"; do
        display "  - On $(format_weekday "$day"): ${VMS_BY_DAY[$day]}"
    done
}

subcommand__scheduled__list() {
    check_generate_script_not_supported
    local pool volume
    list_volume_pools POOLS
    local -A pools_volumes=( )
    for opt in "$@"; do
        check_volume_path "$opt" || usage "Invalid parameter '$opt'"
        pool=$( cut -d '/' -f 1 <<< "$opt" )
        in_array "$pool" "${POOLS[@]}" || \
            usage "Invalid RBD path '$opt': unknown pool '$pool'"
        volume=$( cut -d '/' -f 2 <<< "$opt" )
        [[ "${pools_volumes[$pool]:-null}" == "null" ]] && pools_volumes[$pool]=""
        pools_volumes[$pool]+="$volume "
    done

    # If no pattern is provided or configured, generate it from SCHEDULED_SNAPSHOTS_NAME_FORMAT
    [[ "${SCHEDULED_SNAPSHOTS_NAME_PATTERN:-null}" == "null" ]] && \
        generate_scheduled_snapshots_name_pattern

    local rows='[]' image_pattern pool_rows
    for pool in "${POOLS[@]}"; do
        if [[ ${#pools_volumes[@]} -eq 0 ]]; then
            image_pattern=".*"
        elif [[ "${pools_volumes[$pool]:-null}" == "null" ]]; then
            continue
        else
            read -ra image_names <<< "${pools_volumes[$pool]}"
            image_pattern="^($( implode '|' "${image_names[@]}" ))$"
        fi
        pool_rows=$(
            rbd ls -l "$pool" --format=json | jq -r \
                --arg snap_pattern "$SCHEDULED_SNAPSHOTS_NAME_PATTERN" \
                --arg image_pattern "$image_pattern" \
                --arg pool "$pool" \
            '[
                .[] | select(
                    .snapshot? and (.snapshot | test($snap_pattern))
                    and (.image | test($image_pattern))
                ) | {
                    "pool": $pool,
                    "volume": .image,
                    "snapshot": .snapshot,
                    "protected": .protected,
                }
            ] | sort_by(.image, .snapshot)'
        )
        rows=$( jq ". + $pool_rows" <<< "$rows" )
    done
    print_json_table pool volume snapshot protected <<< "$(
        jq -r 'sort_by(.volume) | sort_by(.snapshot)' <<< "$rows"
    )"
}

subcommand__scheduled__run() {
    local weekday vms=( ) idx=1 day day_vms
    weekday=$(date +%u)
    while [[ $idx -le $# ]]; do
        local opt=${!idx}
        case $opt in
            -D|--weekday)
                ((idx++))
                weekday=${!idx}
                check_weekday "$weekday" || \
                    usage "Invalid weekday '$weekday' specified," \
                        "it should be specified by ID (monday=1,... sunday=7)."
                ;;
            *)
                vms+=( "$opt" )
                ;;
        esac
        ((idx++))
    done

    # The script must run only on the master
    is_master || exit 0

    log_start

    if [[ ${#vms[@]} -eq 0 ]]; then
        get_scheduled_snapshots_planning VMS_BY_DAY

        log INFO "Scheduled snapshots planning:"
        for day in "${SCHEDULED_SNAPSHOTS_DAYS[@]}"; do
            log INFO "  On $(format_weekday "$day"): ${VMS_BY_DAY[$day]}"
        done

        day_vms=$( wc -w <<< "${VMS_BY_DAY[$weekday]}" )
        if [[ $day_vms -eq 0 ]]; then
            log INFO "No VM to snapshot on $(format_weekday "$weekday")"
        else
            log INFO "Run snapshot of the $(format_weekday "$weekday")"
        fi
        read -ra vms <<< "${VMS_BY_DAY[$weekday]}"
    else
        get_scheduled_snapshots_vms_list
        for vm in "${vms[@]}"; do
            in_array "$vm" "${SCHEDULED_SNAPSHOTS_VMS_LIST[@]}" || \
                usage "No scheduled snapshot configured for VM '$vm'"
        done
    fi

    local i=0 vm hyp snap_name state
    for vm in "${vms[@]}"; do
        hyp=$( locate "$vm" )
        [[ -z "$hyp" ]] && log ERROR "VM $vm was found on no hypervisor, ignore it" && continue
        snap_name="$( date "+$SCHEDULED_SNAPSHOTS_NAME_FORMAT" )"
        log INFO "Snapshot VM $vm on $hyp (name: $snap_name)"
        state=$( vm_state "$vm" "$hyp" )
        [[ "$state" != "running" ]] && \
            log WARNING "VM $vm is not running ($state), doing snapshot anyway"
        create_snapshot \
            "$vm" "$hyp" "$snap_name" "${SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[$vm]:-}" 1
        (( i+=1 ))
        if [[ $SCHEDULED_SNAPSHOTS_WAIT_DELAY -gt 0 ]] && [[ $i -lt $day_vms ]]; then
            log INFO "Wait a bit before moving on to the next snapshot" \
                "(${SCHEDULED_SNAPSHOTS_WAIT_DELAY}s)"
            sleep "$SCHEDULED_SNAPSHOTS_WAIT_DELAY"
        fi
    done
    log_stop
}

subcommand__scheduled__cleanup() {
    check_generate_script_not_supported
    local all=0 only=() pool volume
    for opt in "$@"; do
        case $opt in
            ALL)
                all=1
                ;;
            *)
                only+=( "$opt" )
                ;;
        esac
    done

    log_start
    [[ $all -eq 0 ]] && [[ ${#only[@]} -eq 0 ]] && \
        usage "You must provide at least one volume path or use 'all' to cleanup" \
            "all snapshots"

    if [[ $all -eq 1 ]] ; then
        # The command in ALL mode should only be run on the master
        is_master || exit 0

        list_volume_pools POOLS
        for pool in "${POOLS[@]}" ; do
            list_volumes_with_scheduled_snapshots "$pool" VOLUMES
            # shellcheck disable=SC2153
            log INFO "${#VOLUMES[@]} images with scheduled snapshot found in pool $pool"
            for volume in "${VOLUMES[@]}"; do
                cleanup_volume_scheduled_snapshots "${pool}/${volume}"
            done
        done
    else
        for volume in $(list_volumes "${only[@]}"); do
            cleanup_volume_scheduled_snapshots "$volume"
        done
    fi
    log_stop
}

subcommand__scheduled__check() {
    [[ "${SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE:-null}" == "null" ]] && \
        SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE=9
    [[ "${SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE:-null}" == "null" ]] && \
        SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE=12
    [[ "${SCHEDULED_SNAPSHOTS_WARNING_COUNT_THRESHOLD:-null}" == "null" ]] && \
        SCHEDULED_SNAPSHOTS_WARNING_COUNT_THRESHOLD=1
    [[ "${SCHEDULED_SNAPSHOTS_CRITICAL_COUNT_THRESHOLD:-null}" == "null" ]] && \
        SCHEDULED_SNAPSHOTS_CRITICAL_COUNT_THRESHOLD=2

    local idx=1
    while [[ $idx -le $# ]]; do
        local opt=${!idx}
        case $opt in
            -W|--warning-age)
                ((idx++))
                SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE=${!idx}
                check_int "$SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE" 0 || \
                    usage "Invalid warning threshold specified for latest scheduled snapshot " \
                    "age. Must be a positive integer."
                ;;
            -C|--critical-age)
                ((idx++))
                SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE=${!idx}
                check_int "$SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE" 0 || \
                    usage "Invalid critical threshold specified for latest scheduled snapshot " \
                    "age. Must be a positive integer."
                ;;
            -w|--warning-count)
                ((idx++))
                SCHEDULED_SNAPSHOTS_WARNING_COUNT_THRESHOLD=${!idx}
                check_int "$SCHEDULED_SNAPSHOTS_WARNING_COUNT_THRESHOLD" 0 || \
                    usage "Invalid warning scheduled snapshots count threshold" \
                    "specified. Must be a positive integer."
                ;;
            -c|--critical-count)
                ((idx++))
                SCHEDULED_SNAPSHOTS_CRITICAL_COUNT_THRESHOLD=${!idx}
                check_int "$SCHEDULED_SNAPSHOTS_CRITICAL_COUNT_THRESHOLD" 0 || \
                    usage "Invalid critical scheduled snapshots count threshold" \
                    "specified. Must be a positive integer."
                ;;
            *)
                usage "Unknown parameter '$opt'"
                ;;
        esac
        ((idx++))
    done

    declare -Ag ERRORS
    declare -g STATUS=OK EXIT_CODE=0
    local vm hyp volume volumes pool

    function add_error() {
        case "$1" in
            warning)
                [[ EXIT_CODE -lt 1 ]] && STATUS=WARNING && EXIT_CODE=1
                ;;
            critical)
                [[ EXIT_CODE -lt 2 ]] && STATUS=CRITICAL && EXIT_CODE=2
                ;;
        esac
        ERRORS[$2]+="|${*:3}"
    }

    # Compute latest snapshot age thresholds (in seconds)
    latest_warning_age=$(( SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE*24*60*60 ))
    latest_critical_age=$(( SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE*24*60*60 ))
    log DEBUG "Warning/critical thresholds of latest snap age:" \
        "$SCHEDULED_SNAPSHOTS_LATEST_WARNING_AGE days ($latest_warning_age s)" \
        "$SCHEDULED_SNAPSHOTS_LATEST_CRITICAL_AGE days ($latest_critical_age s)"

    # Check configuration
    [[ "${VIP:-null}" == "null" ]] && add_error critical config "VIP not configured"
    check_scheduled_snapshots_name_pattern || add_error critical config \
        "Configured scheduled snapshots name pattern does not match with configured name format"
    check_int "$SCHEDULED_SNAPSHOTS_RETENTION" 0 || add_error critical config \
        "Invalid default retention configured ($SCHEDULED_SNAPSHOTS_RETENTION)," \
        "must be a positive integer."
    if [[ ${#SCHEDULED_SNAPSHOTS_VMS_RETENTION[@]} -eq 0 ]]; then
        for vm in "${!SCHEDULED_SNAPSHOTS_VMS_RETENTION[@]}"; do
            check_int "${SCHEDULED_SNAPSHOTS_VMS_RETENTION[$vm]}" 0 || \
                add_error critical config "Invalid retention value configured for VM $vm" \
                    "(${SCHEDULED_SNAPSHOTS_VMS_RETENTION[$vm]}): must be a positive integer."
        done
    fi
    if [[ ${#SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[@]} -eq 0 ]]; then
        for volume in "${!SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[@]}"; do
            check_int "${SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[$volume]}" 0 || \
                add_error critical config "Invalid retention value configured for shared" \
                    "volume $volume (${SCHEDULED_SNAPSHOTS_SHARED_VOLUMES_RETENTION[$volume]}):" \
                    "must be a positive integer."
        done
    fi

    # Check VM's volumes existing scheduled snapshots count & age
    local volumes_with_snapshots=() snaps_count snaps_count_diff retention_days latest_snap \
        latest_snap_name latest_snap_date latest_snap_timestamp latest_snap_age oldest_snap \
        oldest_snap_name oldest_snap_date oldest_snap_timestamp oldest_snap_age warning_diff_age \
        critical_diff_age vms volume_pattern
    load_volume_to_vm_mapping
    list_volume_pools POOLS
    for pool in "${POOLS[@]}" ; do
        list_volumes_with_scheduled_snapshots "$pool" VOLUMES
        log INFO "${#VOLUMES[@]} images with scheduled snapshots found in pool $pool"
        for volume in "${VOLUMES[@]}"; do
            local volume_path="$pool/$volume"
            volumes_with_snapshots+=( "$volume_path" )
            vm="${VOLUME_TO_VM[$volume_path]:-null}"
            if [[ "$vm" == "null" ]]; then
                if [[ "${SHARED_VOLUMES[$volume_path]:-null}" == "null" ]]; then
                    log INFO "Scheduled snapshots found on volume $volume_path owned by no VM"
                    add_error warning "$volume" \
                        "Scheduled snapshots found on volume $volume_path owned by no VM"
                    continue
                fi
                IFS=" " read -ra vms <<< "${SHARED_VOLUMES[$volume_path]}"
                vm=$( implode " / " "${vms[@]}" )
                log INFO "Check scheduled snapshots of shared volume $volume_path owned by $vm:"
            else
                log INFO "Check scheduled snapshots of volume $volume_path owned by $vm:"
            fi

            if [[ -n "${SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[$vm]:-}" ]]; then
                volume_pattern="${SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[$vm]:-}"
                log DEBUG "VM $vm have configured volumes pattern: '$volume_pattern'"
                if ! check_regex "$volume_path" "$volume_pattern"; then
                    add_error warning "$vm" "Volume $volume_path normally excluded by" \
                        "VM's volume pattern, have snapshot(s)"
                    continue
                else
                    log DEBUG "Volume $volume_path match with VM's volumes pattern"
                fi
            fi

            get_scheduled_snapshots_retention "$volume_path" RETENTION
            log INFO "  Volume $volume_path retention: $RETENTION"
            get_volume_scheduled_snapshots "$volume_path" SNAPS
            # shellcheck disable=SC2153
            snaps_count=$( jq length <<< "$SNAPS" )
            log INFO "  $snaps_count scheduled snapshots found for volume $volume_path"

            # Compute count difference of snapshots and keep the absolute value
            snaps_count_diff=$(( snaps_count-RETENTION ))
            snaps_count_diff=${snaps_count_diff#-}
            log DEBUG \
                "  $snaps_count_diff count difference of snapshots found for volume $volume_path"

            if [[ $snaps_count_diff -ge $SCHEDULED_SNAPSHOTS_CRITICAL_COUNT_THRESHOLD ]]; then
                log INFO "  Critical snapshot count found for $volume ($snaps_count vs $RETENTION)"
                add_error critical "$vm" "Volume $volume_path have $snaps_count snapshot(s)" \
                    "(should have $RETENTION)"
            elif [[ $snaps_count_diff -ge $SCHEDULED_SNAPSHOTS_WARNING_COUNT_THRESHOLD ]]; then
                log INFO "  Warning snapshot count found for $volume ($snaps_count vs $RETENTION)"
                add_error warning "$vm" "Volume $volume_path have $snaps_count snapshot(s)" \
                    "(should have $RETENTION)"
            fi

            # Compute retention in days
            retention_days=$(( RETENTION*2*7+1 ))
            log INFO "  Retention (in days) for snapshots of $volume: $retention_days"

            # Get the latest snapshot name & date
            latest_snap=$( jq -r '[ .[] ] | sort_by(.timestamp) [-1]' <<< "$SNAPS" )
            latest_snap_name=$( jq -r '.name' <<< "$latest_snap" )
            latest_snap_date=$( jq -r '.timestamp' <<< "$latest_snap" )
            latest_snap_timestamp=$( date -d "$latest_snap_date" +%s )
            latest_snap_age=$(( $(date +%s)-latest_snap_timestamp ))
            log INFO "  Latest snapshot of $volume_path is '$latest_snap_name'" \
                "(date: $latest_snap_date, age: $(format_duration $latest_snap_age))"

            if [[ $latest_snap_age -gt $latest_critical_age ]]; then
                log INFO "  Date of the latest snap of $volume_path is critical"
                add_error critical "$vm" \
                    "No snap of ${volume_path} since $(format_duration $latest_snap_age)" \
                    "(latest: ${latest_snap_name} on $latest_snap_date)"
            elif [[ $latest_snap_age -gt $latest_warning_age ]]; then
                log INFO "  Date of the latest snap of $volume_path is warning"
                add_error warning "$vm" \
                    "No snap of ${volume_path} since $(format_duration $latest_snap_age)" \
                    "(latest: ${latest_snap_name} on $latest_snap_date)"
            fi

            # Get the oldest snapshot name & date
            oldest_snap=$( jq -r '[ .[] ] | sort_by(.timestamp) [0]' <<< "$SNAPS" )
            oldest_snap_name=$( jq -r '.name' <<< "$oldest_snap" )
            oldest_snap_date=$( jq -r '.timestamp' <<< "$oldest_snap" )
            oldest_snap_timestamp=$( date -d "$oldest_snap_date" +%s )
            oldest_snap_age=$(( $(date +%s)-oldest_snap_timestamp ))
            log INFO "  Oldest snapshot of $volume_path is '$oldest_snap_name' (date: $oldest_snap_date," \
                "age: $(format_duration $oldest_snap_age))"

            # Compute last snapshot age thresholds
            warning_diff_age=$(( retention_days*24*60*60 ))
            critical_diff_age=$(( 2*warning_diff_age ))

            if [[ $oldest_snap_age -gt $critical_diff_age ]]; then
                log INFO "  Date of last snap on $volume_path is critical"
                add_error critical "$vm" "Volume ${volume_path} has a very old snapshot:" \
                    "${oldest_snap_name} ($oldest_snap_date)"
            elif [[ $oldest_snap_age -gt $warning_diff_age ]]; then
                log INFO "  Date of last snap on $volume_path is warning"
                add_error warning "$vm" "Volume ${volume_path} has an old snapshot:" \
                    "${oldest_snap_name} ($oldest_snap_date)"
            fi
        done
    done

    log INFO "Check all VMs have at least one snapshot for each of its volumes:"
    get_scheduled_snapshots_vms_list
    log DEBUG "Scheduled snapshots VMs list: ${SCHEDULED_SNAPSHOTS_VMS_LIST[*]}"
    for vm in "${SCHEDULED_SNAPSHOTS_VMS_LIST[@]}"; do
        hyp=$(locate "$vm")
        if [[ -z "$hyp" ]]; then
            add_error warning "$vm" \
                "Failed to locate this VM: can't list its volumes to check their snapshots"
            continue
        fi
        log DEBUG "VM $vm located on $hyp"
        mapfile -t volumes < <( list_volumes "$vm" "$hyp" )
        if [[ ${#volumes[@]} -eq 0 ]]; then
            log INFO "  $vm: no volume found!"
            add_error warning "$vm" "No volume found for this VM"
            continue
        fi
        log INFO "  $vm: ${#volumes[@]} volumes found"
        volume_pattern="${SCHEDULED_SNAPSHOTS_VMS_VOLUMES_PATTERN[$vm]:-}"
        for volume in $( list_volumes "$vm" "$hyp"); do
            if [[ -n "$volume_pattern" ]] && ! check_regex "$volume" "$volume_pattern"; then
                log INFO "Volume $volume does not match with VM's volumes pattern, ignore it"
                continue
            fi
            in_array "$volume" "${volumes_with_snapshots[@]}" && continue
            add_error warning "$vm" "No snapshot found for volume $volume"
        done
    done

    if [[ ${#ERRORS[@]} -eq 0 ]]; then
        echo "OK"
        exit 0
    fi

    echo "$STATUS - problems found with number or age of some snapshots"

    local sorted_keys key msgs
    if [[ "${ERRORS[config]:-null}" != null ]]; then
        IFS="|" read -ra msgs <<< "${ERRORS[config]}"
        echo -e "Configuration:\n  - $( implode "\n  - " "${msgs[@]:1}" )"
        unset "ERRORS[config]"
    fi

    mapfile -t sorted_keys <<< "$( implode $'\n' "${!ERRORS[@]}" | sort )"
    for key in "${sorted_keys[@]}"; do
        IFS="|" read -ra msgs <<< "${ERRORS[$key]}"
        echo -e "$key:\n  - $( implode "\n  - " "${msgs[@]:1}" )"
    done
    exit $EXIT_CODE
}

run_subcommand "${COMMAND_ARGS[@]}"
