From 2b96ac0b44ba4ebce72a9331a1f94acc79f69bbf Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Mon, 26 Aug 2024 01:54:55 +0300 Subject: [PATCH] Implement OpenNebula driver --- debian/control | 6 + debian/opennebula-vitastor.install | 3 + debian/opennebula-vitastor.postinst | 7 + debian/opennebula-vitastor.triggers | 4 + opennebula/install.sh | 20 + opennebula/remotes/datastore/vitastor/clone | 77 +++ opennebula/remotes/datastore/vitastor/cp | 135 +++++ .../datastore/vitastor/downloader-patched.sh | 555 ++++++++++++++++++ .../vitastor/downloader-vitastor.sh.diff | 60 ++ opennebula/remotes/datastore/vitastor/export | 114 ++++ opennebula/remotes/datastore/vitastor/mkfs | 127 ++++ opennebula/remotes/datastore/vitastor/monitor | 64 ++ .../remotes/datastore/vitastor/oned.conf.diff | 70 +++ opennebula/remotes/datastore/vitastor/rm | 63 ++ .../remotes/datastore/vitastor/snap_delete | 64 ++ .../remotes/datastore/vitastor/snap_flatten | 71 +++ .../remotes/datastore/vitastor/snap_revert | 72 +++ opennebula/remotes/datastore/vitastor/stat | 1 + .../datastore/vitastor/vmm_execrc.diff | 12 + opennebula/remotes/tm/vitastor/clone | 97 +++ opennebula/remotes/tm/vitastor/context | 1 + opennebula/remotes/tm/vitastor/cpds | 113 ++++ opennebula/remotes/tm/vitastor/delete | 139 +++++ opennebula/remotes/tm/vitastor/failmigrate | 1 + opennebula/remotes/tm/vitastor/ln | 16 + opennebula/remotes/tm/vitastor/mkimage | 120 ++++ opennebula/remotes/tm/vitastor/mkswap | 1 + opennebula/remotes/tm/vitastor/monitor | 1 + opennebula/remotes/tm/vitastor/mv | 1 + opennebula/remotes/tm/vitastor/mvds | 15 + opennebula/remotes/tm/vitastor/postbackup | 1 + .../remotes/tm/vitastor/postbackup_live | 1 + opennebula/remotes/tm/vitastor/postmigrate | 1 + opennebula/remotes/tm/vitastor/prebackup | 152 +++++ opennebula/remotes/tm/vitastor/prebackup_live | 8 + opennebula/remotes/tm/vitastor/premigrate | 1 + opennebula/remotes/tm/vitastor/resize | 81 +++ opennebula/remotes/tm/vitastor/restore | 203 +++++++ opennebula/remotes/tm/vitastor/snap_create | 78 +++ .../remotes/tm/vitastor/snap_create_live | 1 + opennebula/remotes/tm/vitastor/snap_delete | 75 +++ opennebula/remotes/tm/vitastor/snap_revert | 79 +++ opennebula/remotes/vmm/kvm/deploy.vitastor | 15 + opennebula/remotes/vmm/kvm/deploy_vitastor.py | 58 ++ opennebula/remotes/vmm/kvm/restore.vitastor | 37 ++ opennebula/remotes/vmm/kvm/save.vitastor | 45 ++ opennebula/sudoers.d/opennebula-vitastor | 1 + opennebula/vitastor-imageds.conf | 9 + opennebula/vitastor-systemds.conf | 6 + rpm/vitastor-el7.Dockerfile | 2 +- rpm/vitastor-el7.spec | 28 + rpm/vitastor-el8.Dockerfile | 2 +- rpm/vitastor-el8.spec | 28 + rpm/vitastor-el9.Dockerfile | 2 +- rpm/vitastor-el9.spec | 28 + 55 files changed, 2969 insertions(+), 3 deletions(-) create mode 100644 debian/opennebula-vitastor.install create mode 100644 debian/opennebula-vitastor.postinst create mode 100644 debian/opennebula-vitastor.triggers create mode 100755 opennebula/install.sh create mode 100755 opennebula/remotes/datastore/vitastor/clone create mode 100755 opennebula/remotes/datastore/vitastor/cp create mode 100755 opennebula/remotes/datastore/vitastor/downloader-patched.sh create mode 100644 opennebula/remotes/datastore/vitastor/downloader-vitastor.sh.diff create mode 100755 opennebula/remotes/datastore/vitastor/export create mode 100755 opennebula/remotes/datastore/vitastor/mkfs create mode 100755 opennebula/remotes/datastore/vitastor/monitor create mode 100644 opennebula/remotes/datastore/vitastor/oned.conf.diff create mode 100755 opennebula/remotes/datastore/vitastor/rm create mode 100755 opennebula/remotes/datastore/vitastor/snap_delete create mode 100755 opennebula/remotes/datastore/vitastor/snap_flatten create mode 100755 opennebula/remotes/datastore/vitastor/snap_revert create mode 120000 opennebula/remotes/datastore/vitastor/stat create mode 100644 opennebula/remotes/datastore/vitastor/vmm_execrc.diff create mode 100755 opennebula/remotes/tm/vitastor/clone create mode 120000 opennebula/remotes/tm/vitastor/context create mode 100755 opennebula/remotes/tm/vitastor/cpds create mode 100755 opennebula/remotes/tm/vitastor/delete create mode 120000 opennebula/remotes/tm/vitastor/failmigrate create mode 100755 opennebula/remotes/tm/vitastor/ln create mode 100755 opennebula/remotes/tm/vitastor/mkimage create mode 120000 opennebula/remotes/tm/vitastor/mkswap create mode 120000 opennebula/remotes/tm/vitastor/monitor create mode 120000 opennebula/remotes/tm/vitastor/mv create mode 100755 opennebula/remotes/tm/vitastor/mvds create mode 120000 opennebula/remotes/tm/vitastor/postbackup create mode 120000 opennebula/remotes/tm/vitastor/postbackup_live create mode 120000 opennebula/remotes/tm/vitastor/postmigrate create mode 100755 opennebula/remotes/tm/vitastor/prebackup create mode 100755 opennebula/remotes/tm/vitastor/prebackup_live create mode 120000 opennebula/remotes/tm/vitastor/premigrate create mode 100755 opennebula/remotes/tm/vitastor/resize create mode 100755 opennebula/remotes/tm/vitastor/restore create mode 100755 opennebula/remotes/tm/vitastor/snap_create create mode 120000 opennebula/remotes/tm/vitastor/snap_create_live create mode 100755 opennebula/remotes/tm/vitastor/snap_delete create mode 100755 opennebula/remotes/tm/vitastor/snap_revert create mode 100755 opennebula/remotes/vmm/kvm/deploy.vitastor create mode 100755 opennebula/remotes/vmm/kvm/deploy_vitastor.py create mode 100755 opennebula/remotes/vmm/kvm/restore.vitastor create mode 100755 opennebula/remotes/vmm/kvm/save.vitastor create mode 100644 opennebula/sudoers.d/opennebula-vitastor create mode 100644 opennebula/vitastor-imageds.conf create mode 100644 opennebula/vitastor-systemds.conf diff --git a/debian/control b/debian/control index b84fc3b6..97802837 100644 --- a/debian/control +++ b/debian/control @@ -53,3 +53,9 @@ Architecture: amd64 Depends: ${shlibs:Depends}, ${misc:Depends}, vitastor-client (= ${binary:Version}) Description: Vitastor Proxmox Virtual Environment storage plugin Vitastor storage plugin for Proxmox Virtual Environment. + +Package: opennebula-vitastor +Architecture: amd64 +Depends: ${shlibs:Depends}, ${misc:Depends}, vitastor-client, patch, python3, jq +Description: Vitastor OpenNebula storage plugin + Vitastor storage plugin for OpenNebula. diff --git a/debian/opennebula-vitastor.install b/debian/opennebula-vitastor.install new file mode 100644 index 00000000..6acc1f0a --- /dev/null +++ b/debian/opennebula-vitastor.install @@ -0,0 +1,3 @@ +opennebula/remotes var/lib/one/ +opennebula/sudoers.d etc/ +opennebula/install.sh var/lib/one/remotes/datastore/vitastor/ diff --git a/debian/opennebula-vitastor.postinst b/debian/opennebula-vitastor.postinst new file mode 100644 index 00000000..478ed175 --- /dev/null +++ b/debian/opennebula-vitastor.postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +if [ "$1" = "configure" ]; then + /var/lib/one/remotes/datastore/vitastor/install.sh +fi diff --git a/debian/opennebula-vitastor.triggers b/debian/opennebula-vitastor.triggers new file mode 100644 index 00000000..f8bdb3f5 --- /dev/null +++ b/debian/opennebula-vitastor.triggers @@ -0,0 +1,4 @@ +interest /var/lib/one/remotes/datastore/downloader.sh +interest /etc/one/oned.conf +interest /etc/one/vmm_exec/vmm_execrc +interest /etc/apparmor.d/local/abstractions/libvirt-qemu diff --git a/opennebula/install.sh b/opennebula/install.sh new file mode 100755 index 00000000..ff09c2b8 --- /dev/null +++ b/opennebula/install.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -e + +reapply_patch() { + if ! patch -f --dry-run -l -R $1 < $2 >/dev/null; then + if ! patch -l -f $1 < $2; then + echo "ERROR: Failed to patch file $1, please apply the patch $2 manually" + fi + fi +} +echo "Reapplying Vitastor patches to OpenNebula's oned.conf, vmm_execrc and downloader.sh" +reapply_patch /var/lib/one/remotes/datastore/downloader.sh /var/lib/one/remotes/datastore/vitastor/downloader-vitastor.sh.diff +reapply_patch /etc/one/oned.conf /var/lib/one/remotes/datastore/vitastor/oned.conf.diff +reapply_patch /etc/one/vmm_exec/vmm_execrc /var/lib/one/remotes/datastore/vitastor/vmm_execrc.diff +if [ -f /etc/apparmor.d/local/abstractions/libvirt-qemu ]; then + if ! grep -q /etc/vitastor/vitastor.conf /etc/apparmor.d/local/abstractions/libvirt-qemu; then + echo ' "/etc/vitastor/vitastor.conf" r,' >> /etc/apparmor.d/local/abstractions/libvirt-qemu + fi +fi diff --git a/opennebula/remotes/datastore/vitastor/clone b/opennebula/remotes/datastore/vitastor/clone new file mode 100755 index 00000000..f6b3cecd --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/clone @@ -0,0 +1,77 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to copy a VM image (SRC) to the image repository as DST + +# -------- Set up the environment to source common tools & conf ------------ +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh + +# -------- Get cp and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" + +unset i XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/POOL_NAME \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IMAGE_PREFIX \ + /DS_DRIVER_ACTION_DATA/IMAGE/PATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +unset i + +BASE_PATH="${XPATH_ELEMENTS[i++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" +POOL_NAME="${XPATH_ELEMENTS[i++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[i++]}" +SRC="${XPATH_ELEMENTS[i++]}" +SIZE="${XPATH_ELEMENTS[i++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[i++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi +if [ -n "$POOL_NAME" ]; then + CLI="$CLI --pool ${POOL_NAME}" +fi + +SAFE_DIRS="" + +DST="${IMAGE_PREFIX}one-${ID}" + +ssh_exec_and_log "$DST_HOST" "$CLI create --parent $SRC $DST" \ + "Error during $CLI create --parent $SRC $DST in $DST_HOST" + +# FIXME: Is it really required? +ssh_exec_and_log "$DST_HOST" "$CLI flatten $DST" \ + "Error during $CLI create flatten $DST in $DST_HOST" + +echo "$DST raw" diff --git a/opennebula/remotes/datastore/vitastor/cp b/opennebula/remotes/datastore/vitastor/cp new file mode 100755 index 00000000..e756058a --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/cp @@ -0,0 +1,135 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to copy a local image SRC to the image repository as DST + +# -------- Set up the environment to source common tools & conf ------------ + +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh + +# -------- Get cp and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +export DRV_ACTION + +UTILS_PATH="${DRIVER_PATH}/.." + +XPATH="$UTILS_PATH/xpath.rb -b $DRV_ACTION" + +unset i XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/RESTRICTED_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SAFE_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/POOL_NAME \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IMAGE_PREFIX \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/STAGING_DIR \ + /DS_DRIVER_ACTION_DATA/IMAGE/PATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/MD5 \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/SHA1 \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/NO_DECOMPRESS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/LIMIT_TRANSFER_BW \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +unset i + +BASE_PATH="${XPATH_ELEMENTS[i++]}" +RESTRICTED_DIRS="${XPATH_ELEMENTS[i++]}" +SAFE_DIRS="${XPATH_ELEMENTS[i++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" +POOL_NAME="${XPATH_ELEMENTS[i++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[i++]}" +STAGING_DIR="${XPATH_ELEMENTS[i++]:-$STAGING_DIR}" +SRC="${XPATH_ELEMENTS[i++]}" +SIZE="${XPATH_ELEMENTS[i++]}" +MD5="${XPATH_ELEMENTS[i++]}" +SHA1="${XPATH_ELEMENTS[i++]}" +NO_DECOMPRESS="${XPATH_ELEMENTS[i++]}" +LIMIT_TRANSFER_BW="${XPATH_ELEMENTS[i++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[i++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +CLI=vitastor-cli +QEMU_ARG="" +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" + QEMU_ARG=":config_path=${VITASTOR_CONF}" +fi +if [ -n "$POOL_NAME" ]; then + CLI="$CLI --pool ${POOL_NAME}" +fi + +set_up_datastore "$BASE_PATH" "$RESTRICTED_DIRS" "$SAFE_DIRS" + +IMAGE_HASH=`generate_image_hash` +TMP_DST="$STAGING_DIR/$IMAGE_HASH" + +DST="${IMAGE_PREFIX}one-${ID}" + +DOWNLOADER_ARGS=`set_downloader_args "$MD5" "$SHA1" "$NO_DECOMPRESS" "$LIMIT_TRANSFER_BW" "$SRC" -` + +COPY_COMMAND="$UTILS_PATH/downloader.sh $DOWNLOADER_ARGS" + +case $SRC in +http://*|https://*) + log "Downloading $SRC to the image repository" + + DUMP="$COPY_COMMAND" + ;; + +*) + if [ `check_restricted $SRC` -eq 1 ]; then + log_error "Not allowed to copy images from $RESTRICTED_DIRS" + error_message "Not allowed to copy image file $SRC" + exit -1 + fi + + log "Copying local image $SRC to the image repository" + + DUMP="$COPY_COMMAND" + ;; +esac + +multiline_exec_and_log "set -e -o pipefail; $DUMP | $SSH $DST_HOST $DD of=$TMP_DST bs=1M" \ + "Error copying $SRC to $DST_HOST:$TMP_DST" + +REGISTER_CMD=$(cat </dev/null + fi +} + +# Gets the command needed to decompress an stream. +function get_decompressor +{ + type=$1 + + case "$type" in + "application/x-gzip"|"application/gzip") + echo "gunzip -c -" + ;; + "application/x-bzip2") + echo "bunzip2 -qc -" + ;; + "application/x-xz") + echo "unxz -c -" + ;; + *) + echo "cat" + ;; + esac +} + +# Function called to decompress a stream. The first parameter is the command +# used to decompress the stream. Second parameter is the output file or +# - for stdout. +function decompress +{ + command="$1" + to="$2" + + if [ "$to" = "-" ]; then + $command + else + $command > "$to" + fi +} + +# Function called to hash a stream. First parameter is the algorithm name. +function hasher +{ + if [ -n "$1" ]; then + openssl dgst -$1 | awk '{print $NF}' > $HASH_FILE + else + # Needs something consuming stdin or the pipe will break + cat >/dev/null + fi +} + +# Unarchives a tar or a zip a file to a directory with the same name. +function unarchive +{ + TO="$1" + + file_type=$(get_type "cat $TO") + + tmp="$TO" + + # Add full path if it is relative + if [ ${tmp:0:1} != "/" ]; then + tmp="$PWD/$tmp" + fi + + IN="$tmp.tmp" + OUT="$tmp" + + case "$file_type" in + "application/x-tar") + command="tar -xf $IN -C $OUT" + ;; + "application/zip") + command="unzip -d $OUT $IN" + ;; + *) + command="" + ;; + esac + + if [ -n "$command" ]; then + mv "$OUT" "$IN" + mkdir "$OUT" + + $command + + if [ "$?" != "0" ]; then + echo "Error uncompressing archive" >&2 + exit -1 + fi + + rm "$IN" + fi +} + +function s3_env +{ + XPATH="$DRIVER_PATH/xpath.rb -b $DRV_ACTION" + + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <($XPATH /DS_DRIVER_ACTION_DATA/MARKETPLACE/TEMPLATE/ACCESS_KEY_ID \ + /DS_DRIVER_ACTION_DATA/MARKETPLACE/TEMPLATE/SECRET_ACCESS_KEY \ + /DS_DRIVER_ACTION_DATA/MARKETPLACE/TEMPLATE/REGION \ + /DS_DRIVER_ACTION_DATA/MARKETPLACE/TEMPLATE/AWS \ + /DS_DRIVER_ACTION_DATA/MARKETPLACE/TEMPLATE/ENDPOINT) + + S3_ACCESS_KEY_ID="${XPATH_ELEMENTS[j++]}" + S3_SECRET_ACCESS_KEY="${XPATH_ELEMENTS[j++]}" + S3_REGION="${XPATH_ELEMENTS[j++]}" + S3_AWS="${XPATH_ELEMENTS[j++]}" + S3_ENDPOINT="${XPATH_ELEMENTS[j++]}" + + CURRENT_DATE_DAY="$(date -u '+%Y%m%d')" + CURRENT_DATE_ISO8601="${CURRENT_DATE_DAY}T$(date -u '+%H%M%S')Z" +} + +# Create an SHA-256 hash in hexadecimal. +# Usage: +# hash_sha256 +function hash_sha256 { + printf "${1}" | openssl dgst -sha256 | sed 's/^.* //' +} + +# Create an SHA-256 hmac in hexadecimal. +# Usage: +# hmac_sha256 +function hmac_sha256 { + printf "${2}" | openssl dgst -sha256 -mac HMAC -macopt "${1}" | sed 's/^.* //' +} + +# Create the signature. +# Usage: +# create_signature +function create_signature { + stringToSign="AWS4-HMAC-SHA256\n${CURRENT_DATE_ISO8601}\n${CURRENT_DATE_DAY}/${S3_REGION}/s3/aws4_request\n$(hash_sha256 "${HTTP_CANONICAL_REQUEST}")" + dateKey=$(hmac_sha256 key:"AWS4${S3_SECRET_ACCESS_KEY}" "${CURRENT_DATE_DAY}") + regionKey=$(hmac_sha256 hexkey:"${dateKey}" "${S3_REGION}") + serviceKey=$(hmac_sha256 hexkey:"${regionKey}" "s3") + signingKey=$(hmac_sha256 hexkey:"${serviceKey}" "aws4_request") + + printf "${stringToSign}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:"${signingKey}" | sed 's/.*(stdin)= //' +} + +function s3_curl_args +{ + FROM="$1" + + ENDPOINT="$S3_ENDPOINT" + OBJECT=$(basename "$FROM") + BUCKET=$(basename $(dirname "$FROM")) + + DATE="`date -u +'%a, %d %b %Y %H:%M:%S GMT'`" + AUTH_STRING="GET\n\n\n${DATE}\n/${BUCKET}/${OBJECT}" + + SIGNED_AUTH_STRING=`echo -en "$AUTH_STRING" | \ + openssl sha1 -hmac ${S3_SECRET_ACCESS_KEY} -binary | \ + base64` + + echo " -H \"Date: ${DATE}\"" \ + " -H \"Authorization: AWS ${S3_ACCESS_KEY_ID}:${SIGNED_AUTH_STRING}\"" \ + " '$(esc_sq "${ENDPOINT}/${BUCKET}/${OBJECT}")'" +} + +function s3_curl_args_aws +{ + FROM="$1" + + OBJECT=$(basename "$FROM") + BUCKET=$(basename "$(dirname "$FROM")") + + ENDPOINT="$BUCKET.s3.amazonaws.com" + + AWS_S3_PATH="$(echo $OBJECT | sed 's;^\([^/]\);/\1;')" + + HTTP_REQUEST_PAYLOAD_HASH="$(echo "" | openssl dgst -sha256 | sed 's/^.* //')" + HTTP_CANONICAL_REQUEST_URI="${AWS_S3_PATH}" + HTTP_REQUEST_CONTENT_TYPE='application/octet-stream' + + HTTP_CANONICAL_REQUEST_HEADERS="content-type:${HTTP_REQUEST_CONTENT_TYPE} +host:${ENDPOINT} +x-amz-content-sha256:${HTTP_REQUEST_PAYLOAD_HASH} +x-amz-date:${CURRENT_DATE_ISO8601}" + + HTTP_REQUEST_SIGNED_HEADERS="content-type;host;x-amz-content-sha256;x-amz-date" +HTTP_CANONICAL_REQUEST="GET +${HTTP_CANONICAL_REQUEST_URI}\n +${HTTP_CANONICAL_REQUEST_HEADERS}\n +${HTTP_REQUEST_SIGNED_HEADERS} +${HTTP_REQUEST_PAYLOAD_HASH}" + + SIGNATURE="$(create_signature)" + HTTP_REQUEST_AUTHORIZATION_HEADER="AWS4-HMAC-SHA256 Credential=${S3_ACCESS_KEY_ID}/${CURRENT_DATE_DAY}/${S3_REGION}/s3/aws4_request, SignedHeaders=${HTTP_REQUEST_SIGNED_HEADERS}, Signature=${SIGNATURE}" + + echo " -H \"Authorization: ${HTTP_REQUEST_AUTHORIZATION_HEADER}\"" \ + " -H \"content-type: ${HTTP_REQUEST_CONTENT_TYPE}\"" \ + " -H \"x-amz-content-sha256: ${HTTP_REQUEST_PAYLOAD_HASH}\"" \ + " -H \"x-amz-date: ${CURRENT_DATE_ISO8601}\"" \ + " \"https://${ENDPOINT}${HTTP_CANONICAL_REQUEST_URI}\"" +} + +function get_rbd_cmd +{ + local i j URL_ELEMENTS + + FROM="$1" + + URL_RB="$DRIVER_PATH/url.rb" + + while IFS= read -r -d '' element; do + URL_ELEMENTS[i++]="$element" + done < <($URL_RB "$FROM" \ + USER \ + HOST \ + SOURCE \ + PARAM_DS \ + PARAM_CEPH_USER \ + PARAM_CEPH_KEY \ + PARAM_CEPH_CONF) + + USER="${URL_ELEMENTS[j++]}" + DST_HOST="${URL_ELEMENTS[j++]}" + SOURCE="${URL_ELEMENTS[j++]}" + DS="${URL_ELEMENTS[j++]}" + CEPH_USER="${URL_ELEMENTS[j++]}" + CEPH_KEY="${URL_ELEMENTS[j++]}" + CEPH_CONF="${URL_ELEMENTS[j++]}" + + # Remove leading '/' + SOURCE="${SOURCE#/}" + + if [ -n "$USER" ]; then + DST_HOST="$USER@$DST_HOST" + fi + + if [ -n "$CEPH_USER" ]; then + RBD="$RBD --id '$(esc_sq "${CEPH_USER}")'" + fi + + if [ -n "$CEPH_KEY" ]; then + RBD="$RBD --keyfile '$(esc_sq "${CEPH_KEY}")'" + fi + + if [ -n "$CEPH_CONF" ]; then + RBD="$RBD --conf '$(esc_sq "${CEPH_CONF}")'" + fi + + echo "ssh '$(esc_sq "$DST_HOST")' \"$RBD export '$(esc_sq "$SOURCE")' -\"" +} + +function get_vitastor_cmd +{ + local i j URL_ELEMENTS + + FROM="$1" + + URL_RB="$DRIVER_PATH/url.rb" + + while IFS= read -r -d '' element; do + URL_ELEMENTS[i++]="$element" + done < <($URL_RB "$FROM" \ + USER \ + HOST \ + SOURCE \ + PARAM_DS \ + PARAM_VITASTOR_CONF) + + USER="${URL_ELEMENTS[j++]}" + DST_HOST="${URL_ELEMENTS[j++]}" + SOURCE="${URL_ELEMENTS[j++]}" + DS="${URL_ELEMENTS[j++]}" + VITASTOR_CONF="${URL_ELEMENTS[j++]}" + + # Remove leading '/' + SOURCE="${SOURCE#/}" + + if [ -n "$USER" ]; then + DST_HOST="$USER@$DST_HOST" + fi + + local CLI + CLI="vitastor-cli" + if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path '$(esc_sq "${VITASTOR_CONF}")'" + fi + + echo "ssh '$(esc_sq "$DST_HOST")' \"$CLI dd iimg='$(esc_sq "$SOURCE")'\"" +} + +# Compare 2 version strings using sort -V +# Usage: +# verlte "3.2.9" "3.4.0" +function verlte() { + [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] +} + +# Returns curl retry options based on its version +function curl_retry_args { + [ "$NO_RETRY" = "yes" ] && return + + RETRY_ARGS="--retry 3 --retry-delay 3" + + CURL_VER=`curl --version | grep -o 'curl [0-9\.]*' | awk '{print $2}'` + + # To retry also on conn-reset-by-peer fresh curl is needed + if verlte "7.71.0" "$CURL_VER" && [ -z ${MAX_SIZE} ] ; then + RETRY_ARGS+=" --retry-all-errors" + fi + + echo $RETRY_ARGS +} + +TEMP=`getopt -o m:s:l:c:no -l md5:,sha1:,limit:,max-size:,nodecomp,noretry -- "$@"` + +if [ $? != 0 ] ; then + echo "Arguments error" >&2 + exit -1 +fi + +eval set -- "$TEMP" + +while true; do + case "$1" in + -m|--md5) + HASH_TYPE=md5 + HASH=$2 + shift 2 + ;; + -s|--sha1) + HASH_TYPE=sha1 + HASH=$2 + shift 2 + ;; + -n|--nodecomp) + export NO_DECOMPRESS="yes" + shift + ;; + -l|--limit) + export LIMIT_RATE="$2" + shift 2 + ;; + -c|--max-size) + export MAX_SIZE="$2" + shift 2 + ;; + -o|--noretry) + export NO_RETRY="yes" + shift + ;; + --) + shift + break + ;; + *) + shift + ;; + esac +done + +FROM="$1" +TO="$2" + +if [ -n "${HASH_TYPE}" -a -n "${MAX_SIZE}" ]; then + echo "Hash check not supported for partial downloads" >&2 + exit -1 +else + # File used by the hasher function to store the resulting hash + export HASH_FILE="/tmp/downloader.hash.$$" +fi + +GLOBAL_CURL_ARGS="--fail -sS -k -L $(curl_retry_args)" + +case "$FROM" in +http://*|https://*) + # -k so it does not check the certificate + # -L to follow redirects + # -sS to hide output except on failure + # --limit_rate to limit the bw + curl_args="$GLOBAL_CURL_ARGS '$(esc_sq "${FROM}")'" + + if [ -n "$LIMIT_RATE" ]; then + curl_args="--limit-rate $LIMIT_RATE $curl_args" + fi + + command="curl $curl_args" + ;; +ssh://*) + # pseudo-url for ssh transfers ssh://user@host:path + # -l to limit the bw + ssh_src=${FROM#ssh://} + ssh_arg=(${ssh_src/:/ }) + + rmt_cmd="\"cat '$(esc_sq "${ssh_arg[1]}")'\"" + + command="ssh ${ssh_arg[0]} $rmt_cmd" + ;; +s3://*) + # Read s3 environment + s3_env + + if [ -z "$S3_ACCESS_KEY_ID" -o -z "$S3_SECRET_ACCESS_KEY" ]; then + echo "S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are required" >&2 + exit -1 + fi + + curl_args="" + + if [[ "$S3_AWS" =~ (no|NO) ]]; then + curl_args="$(s3_curl_args "$FROM")" + else + curl_args="$(s3_curl_args_aws "$FROM")" + fi + + command="curl $GLOBAL_CURL_ARGS $curl_args" + ;; +rbd://*) + command="$(get_rbd_cmd "$FROM")" + ;; +vitastor://*) + command="$(get_vitastor_cmd "$FROM")" + ;; +vcenter://*) + command="$VAR_LOCATION/remotes/datastore/vcenter_downloader.rb '$(esc_sq "$FROM")'" + ;; +lxd://*) + file_type="application/octet-stream" + command="$VAR_LOCATION/remotes/datastore/lxd_downloader.sh \"$FROM\"" + ;; +restic://*) + eval `$VAR_LOCATION/remotes/datastore/restic_downloader.rb "$FROM" | grep -e '^command=' -e '^clean_command='` + ;; +rsync://*) + eval `$VAR_LOCATION/remotes/datastore/rsync_downloader.rb "$FROM" | grep -e '^command=' -e '^clean_command='` + ;; +*) + if [ ! -r $FROM ]; then + echo "Cannot read from $FROM" >&2 + exit -1 + fi + command="cat '$(esc_sq "$FROM")'" + ;; +esac + +[ -z "$file_type" ] && file_type=$(get_type "$command") +decompressor=$(get_decompressor "$file_type") + +if [ -z "${MAX_SIZE}" ]; then + eval "$command" | \ + tee >( hasher $HASH_TYPE) | \ + decompress "$decompressor" "$TO" + + if [ "$?" != "0" -o "$PIPESTATUS" != "0" ]; then + echo "Error copying" >&2 + exit -1 + fi +else + # Order of the 'head' command is here on purpose: + # 1. We want to download more bytes than needed to get a requested + # number of bytes on the output. Decompressor may need more + # data to decompress the stream. + # 2. Decompressor command is also misused to detect SIGPIPE error. + eval "$command" | \ + decompress "$decompressor" "$TO" 2>/dev/null | \ + head -c "${MAX_SIZE}" + + # Following table shows exit codes of each command + # in the pipe for various scenarios: + # + # ---------------------------------------------------- + # | $COMMAND | TYPE | PIPESTATUS | BEHAVIOUR + # ---------------------------------------------------- + # | cat | partial | 141 141 0 | OK + # | cat | full | 0 0 0 | OK + # | cat | error | 1 0 0 | fail + # | curl | partial | 23 141 0 | OK + # | curl | full | 0 0 0 | OK + # | curl | error | 22 0 0 | fail + # | ssh | partial | 255 141 0 | OK + # | ssh | full | 0 0 0 | OK + # | ssh | error ssh | 255 0 0 | fail + # | ssh | error ssh cat | 1 0 0 | fail + if [ \( "${PIPESTATUS[0]}" != '0' -a "${PIPESTATUS[1]}" = '0' \) \ + -o \( "${PIPESTATUS[1]}" != '0' -a "${PIPESTATUS[1]}" != '141' \) \ + -o \( "${PIPESTATUS[2]}" != "0" \) ]; + then + echo "Error copying" >&2 + exit -1 + fi +fi + +if [ -n "$HASH_TYPE" ]; then + HASH_RESULT=$( cat $HASH_FILE) + rm $HASH_FILE + if [ "$HASH_RESULT" != "$HASH" ]; then + echo "Hash does not match" >&2 + exit -1 + fi +fi + +# Unarchive only if the destination is filesystem +if [ "$TO" != "-" ]; then + unarchive "$TO" +fi + +# Perform any clean operation +if [ -n "${clean_command}" ]; then + eval "$clean_command" +fi diff --git a/opennebula/remotes/datastore/vitastor/downloader-vitastor.sh.diff b/opennebula/remotes/datastore/vitastor/downloader-vitastor.sh.diff new file mode 100644 index 00000000..351fcdce --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/downloader-vitastor.sh.diff @@ -0,0 +1,60 @@ +diff --git /var/lib/one/remotes/datastore/downloader.sh /var/lib/one/remotes/datastore/downloader.sh +index 9b75d8ee4b..09d2a5d41d 100755 +--- /var/lib/one/remotes/datastore/downloader.sh ++++ /var/lib/one/remotes/datastore/downloader.sh +@@ -295,6 +295,45 @@ function get_rbd_cmd + echo "ssh '$(esc_sq "$DST_HOST")' \"$RBD export '$(esc_sq "$SOURCE")' -\"" + } + ++function get_vitastor_cmd ++{ ++ local i j URL_ELEMENTS ++ ++ FROM="$1" ++ ++ URL_RB="$DRIVER_PATH/url.rb" ++ ++ while IFS= read -r -d '' element; do ++ URL_ELEMENTS[i++]="$element" ++ done < <($URL_RB "$FROM" \ ++ USER \ ++ HOST \ ++ SOURCE \ ++ PARAM_DS \ ++ PARAM_VITASTOR_CONF) ++ ++ USER="${URL_ELEMENTS[j++]}" ++ DST_HOST="${URL_ELEMENTS[j++]}" ++ SOURCE="${URL_ELEMENTS[j++]}" ++ DS="${URL_ELEMENTS[j++]}" ++ VITASTOR_CONF="${URL_ELEMENTS[j++]}" ++ ++ # Remove leading '/' ++ SOURCE="${SOURCE#/}" ++ ++ if [ -n "$USER" ]; then ++ DST_HOST="$USER@$DST_HOST" ++ fi ++ ++ local CLI ++ CLI="vitastor-cli" ++ if [ -n "$VITASTOR_CONF" ]; then ++ CLI="$CLI --config_path '$(esc_sq "${VITASTOR_CONF}")'" ++ fi ++ ++ echo "ssh '$(esc_sq "$DST_HOST")' \"$CLI dd iimg='$(esc_sq "$SOURCE")'\"" ++} ++ + # Compare 2 version strings using sort -V + # Usage: + # verlte "3.2.9" "3.4.0" +@@ -424,6 +463,9 @@ s3://*) + rbd://*) + command="$(get_rbd_cmd "$FROM")" + ;; ++vitastor://*) ++ command="$(get_vitastor_cmd "$FROM")" ++ ;; + vcenter://*) + command="$VAR_LOCATION/remotes/datastore/vcenter_downloader.rb '$(esc_sq "$FROM")'" + ;; diff --git a/opennebula/remotes/datastore/vitastor/export b/opennebula/remotes/datastore/vitastor/export new file mode 100755 index 00000000..64d3cb15 --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/export @@ -0,0 +1,114 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to export an image to qcow2 file + +# ------------ Set up the environment to source common tools ------------ + +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh + +# -------- Get rm and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" + +unset i XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE \ + /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/MD5 \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/SHA1 \ + /DS_DRIVER_ACTION_DATA/IMAGE/TEMPLATE/FORMAT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +unset i + +SRC="${XPATH_ELEMENTS[i++]}" +SIZE="${XPATH_ELEMENTS[i++]}" +MD5="${XPATH_ELEMENTS[i++]}" +SHA1="${XPATH_ELEMENTS[i++]}" +FORMAT="${XPATH_ELEMENTS[i++]:-raw}" +BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[i++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +IMPORT_SOURCE="vitastor://$DST_HOST/$SRC" +IS_JOIN="?" + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path $VITASTOR_CONF" + IMPORT_SOURCE="${IMPORT_SOURCE}${IS_JOIN}VITASTOR_CONF=${VITASTOR_CONF}" +fi + +# FIXME: this is inefficient - it pipes the image twice... + +INFO_SCRIPT=$(cat < + + +EOT +EOF +) + +INFO=$(ssh_monitor_and_log "$DST_HOST" "$INFO_SCRIPT" "Image info script" 2>&1) +INFO_STATUS=$? + +if [ "$INFO_STATUS" != "0" ]; then + echo "$INFO" + exit $INFO_STATUS +fi + +cat < + + $INFO + NO +" +EOF diff --git a/opennebula/remotes/datastore/vitastor/mkfs b/opennebula/remotes/datastore/vitastor/mkfs new file mode 100755 index 00000000..cf63abc7 --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/mkfs @@ -0,0 +1,127 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to create a VM image (SRC) of size (SIZE) and formatted as (FS) + +# -------- Set up the environment to source common tools & conf ------------ + +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) +source ${DRIVER_PATH}/../libfs.sh +source ${DRIVER_PATH}/../../etc/datastore/datastore.conf + +# -------- Get mkfs and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" + +unset i XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/BASE_PATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/RESTRICTED_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/SAFE_DIRS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/POOL_NAME \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IMAGE_PREFIX \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/STAGING_DIR \ + /DS_DRIVER_ACTION_DATA/IMAGE/FORMAT \ + /DS_DRIVER_ACTION_DATA/IMAGE/SIZE \ + /DS_DRIVER_ACTION_DATA/IMAGE/FS \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +unset i + +BASE_PATH="${XPATH_ELEMENTS[i++]}" +RESTRICTED_DIRS="${XPATH_ELEMENTS[i++]}" +SAFE_DIRS="${XPATH_ELEMENTS[i++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" +POOL_NAME="${XPATH_ELEMENTS[i++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[i++]}" +STAGING_DIR="${XPATH_ELEMENTS[i++]:-$STAGING_DIR}" +FORMAT="${XPATH_ELEMENTS[i++]}" +SIZE="${XPATH_ELEMENTS[i++]}" +FS="${XPATH_ELEMENTS[i++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[i++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +CLI= +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi +if [ -n "$POOL_NAME" ]; then + CLI="$CLI --pool ${POOL_NAME}" +fi + +set_up_datastore "$BASE_PATH" "$RESTRICTED_DIRS" "$SAFE_DIRS" + +IMAGE_NAME="${IMAGE_PREFIX}one-${ID}" + +# ------------ Image to save_as disk, no need to create a new image ------------ + +if [ "$FORMAT" = "save_as" ]; then + echo "$IMAGE_NAME" + exit 0 +fi + +# ------------ Create the image in the repository ------------ + +# FIXME: Duplicate code with tm/vitastor/mkimage + +MKIMAGE_CMD=$(cat <&1" "Error monitoring ${POOL_NAME} in $HOST" diff --git a/opennebula/remotes/datastore/vitastor/oned.conf.diff b/opennebula/remotes/datastore/vitastor/oned.conf.diff new file mode 100644 index 00000000..43448b66 --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/oned.conf.diff @@ -0,0 +1,70 @@ +diff --git /etc/one/oned.conf /etc/one/oned.conf +index be02d646a8..27f876ec36 100644 +--- /etc/one/oned.conf ++++ /etc/one/oned.conf +@@ -481,7 +481,7 @@ VM_MAD = [ + NAME = "kvm", + SUNSTONE_NAME = "KVM", + EXECUTABLE = "one_vmm_exec", +- ARGUMENTS = "-t 15 -r 0 kvm -p", ++ ARGUMENTS = "-t 15 -r 0 kvm -p -l deploy=deploy.vitastor", + DEFAULT = "vmm_exec/vmm_exec_kvm.conf", + TYPE = "kvm", + KEEP_SNAPSHOTS = "yes", +@@ -592,7 +592,7 @@ VM_MAD = [ + + TM_MAD = [ + EXECUTABLE = "one_tm", +- ARGUMENTS = "-t 15 -d dummy,lvm,shared,fs_lvm,fs_lvm_ssh,qcow2,ssh,ceph,dev,vcenter,iscsi_libvirt" ++ ARGUMENTS = "-t 15 -d dummy,lvm,shared,fs_lvm,fs_lvm_ssh,qcow2,ssh,ceph,vitastor,dev,vcenter,iscsi_libvirt" + ] + + #******************************************************************************* +@@ -612,7 +612,7 @@ TM_MAD = [ + + DATASTORE_MAD = [ + EXECUTABLE = "one_datastore", +- ARGUMENTS = "-t 15 -d dummy,fs,lvm,ceph,dev,iscsi_libvirt,vcenter,restic,rsync -s shared,ssh,ceph,fs_lvm,fs_lvm_ssh,qcow2,vcenter" ++ ARGUMENTS = "-t 15 -d dummy,fs,lvm,ceph,vitastor,dev,iscsi_libvirt,vcenter,restic,rsync -s shared,ssh,ceph,vitastor,fs_lvm,fs_lvm_ssh,qcow2,vcenter" + ] + + #******************************************************************************* +@@ -1050,6 +1050,9 @@ INHERIT_DATASTORE_ATTR = "VCENTER_DS_IMAGE_DIR" + INHERIT_DATASTORE_ATTR = "VCENTER_DS_VOLATILE_DIR" + INHERIT_DATASTORE_ATTR = "VCENTER_INSTANCE_ID" + ++INHERIT_DATASTORE_ATTR = "VITASTOR_CONF" ++INHERIT_DATASTORE_ATTR = "IMAGE_PREFIX" ++ + INHERIT_IMAGE_ATTR = "DISK_TYPE" + INHERIT_IMAGE_ATTR = "VCENTER_ADAPTER_TYPE" + INHERIT_IMAGE_ATTR = "VCENTER_DISK_TYPE" +@@ -1180,6 +1183,14 @@ TM_MAD_CONF = [ + CLONE_TARGET_SHARED = "SELF", DISK_TYPE_SHARED = "RBD" + ] + ++TM_MAD_CONF = [ ++ NAME = "vitastor", LN_TARGET = "NONE", CLONE_TARGET = "SELF", SHARED = "YES", ++ DS_MIGRATE = "NO", DRIVER = "raw", ALLOW_ORPHANS="format", ++ TM_MAD_SYSTEM = "ssh,shared", LN_TARGET_SSH = "SYSTEM", CLONE_TARGET_SSH = "SYSTEM", ++ DISK_TYPE_SSH = "FILE", LN_TARGET_SHARED = "NONE", ++ CLONE_TARGET_SHARED = "SELF", DISK_TYPE_SHARED = "FILE" ++] ++ + TM_MAD_CONF = [ + NAME = "iscsi_libvirt", LN_TARGET = "NONE", CLONE_TARGET = "SELF", SHARED = "YES", + DS_MIGRATE = "NO", DRIVER = "raw" +@@ -1219,6 +1230,13 @@ DS_MAD_CONF = [ + MARKETPLACE_ACTIONS = "export" + ] + ++DS_MAD_CONF = [ ++ NAME = "vitastor", ++ REQUIRED_ATTRS = "DISK_TYPE,BRIDGE_LIST", ++ PERSISTENT_ONLY = "NO", ++ MARKETPLACE_ACTIONS = "export" ++] ++ + DS_MAD_CONF = [ + NAME = "dev", REQUIRED_ATTRS = "DISK_TYPE", PERSISTENT_ONLY = "YES" + ] diff --git a/opennebula/remotes/datastore/vitastor/rm b/opennebula/remotes/datastore/vitastor/rm new file mode 100755 index 00000000..d848c4b7 --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/rm @@ -0,0 +1,63 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to remove a VM image from the image repository + +# ------------ Set up the environment to source common tools ------------ +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) + +source ${DRIVER_PATH}/../libfs.sh + +# -------- Get rm and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +IMAGE_NAME="${XPATH_ELEMENTS[j++]}" +BRIDGE_LIST="${XPATH_ELEMENTS[j++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi + +# -------- Remove Image from Datastore ------------ + +log "Removing $IMAGE_NAME from the image repository in $DST_HOST" + +DELETE_CMD=$(cat < 0' + $CLI rm ${IMAGE_NAME}.flatten || true + $CLI create --pool "${POOL_NAME}" --parent ${IMAGE_NAME}@${SNAP_ID} ${IMAGE_NAME}.flatten + $CLI flatten ${IMAGE_NAME}.flatten + $CLI rm ${IMAGE_NAME} || true + $CLI modify ${IMAGE_NAME}.flatten --rename ${IMAGE_NAME} +EOF +) + +ssh_exec_and_log "$DST_HOST" "$SNAP_FLATTEN_CMD" "Error flattening snapshot $SNAP_ID for $IMAGE_NAME" diff --git a/opennebula/remotes/datastore/vitastor/snap_revert b/opennebula/remotes/datastore/vitastor/snap_revert new file mode 100755 index 00000000..4bab9c05 --- /dev/null +++ b/opennebula/remotes/datastore/vitastor/snap_revert @@ -0,0 +1,72 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# This script is used to revert a snapshot of an image + +# -------- Set up the environment to source common tools & conf ------------ + +if [ -z "${ONE_LOCATION}" ]; then + LIB_LOCATION=/usr/lib/one +else + LIB_LOCATION=$ONE_LOCATION/lib +fi + +. $LIB_LOCATION/sh/scripts_common.sh + +DRIVER_PATH=$(dirname $0) + +source ${DRIVER_PATH}/../libfs.sh + +# -------- Get image and datastore arguments from OpenNebula core ------------ + +DRV_ACTION=`cat -` +ID=$1 + +XPATH="${DRIVER_PATH}/../xpath.rb -b $DRV_ACTION" + +unset i XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/BRIDGE_LIST \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/POOL_NAME \ + /DS_DRIVER_ACTION_DATA/IMAGE/SOURCE \ + /DS_DRIVER_ACTION_DATA/IMAGE/TARGET_SNAPSHOT \ + /DS_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF) + +unset i + +BRIDGE_LIST="${XPATH_ELEMENTS[i++]}" +POOL_NAME="${XPATH_ELEMENTS[i++]}" +IMAGE_NAME="${XPATH_ELEMENTS[i++]}" +SNAP_ID="${XPATH_ELEMENTS[i++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[i++]}" + +DST_HOST=`get_destination_host $ID` + +if [ -z "$DST_HOST" ]; then + error_message "Datastore template missing 'BRIDGE_LIST' attribute." + exit -1 +fi + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi +if [ -n "$POOL_NAME" ]; then + CLI="$CLI --pool ${POOL_NAME}" +fi + +SNAP_REVERT_CMD=$(cat < +# - host is the target host to deploy the VM +# - remote_system_ds is the path for the system datastore in the host + +DST=$1 +VM_ID=$2 +DS_ID=$3 + +#-------------------------------------------------------------------------------- + +if [ -z "${ONE_LOCATION}" ]; then + TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh + LIB_LOCATION=/usr/lib/one +else + TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh + LIB_LOCATION=$ONE_LOCATION/lib +fi + +DRIVER_PATH=$(dirname $0) + +source $TMCOMMON +source ${DRIVER_PATH}/../../datastore/libfs.sh + +#------------------------------------------------------------------------------- +# Process destination +#------------------------------------------------------------------------------- + +DST_PATH=`arg_path $DST` +DST_HOST=`arg_host $DST` + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +#------------------------------------------------------------------------------- +# Delete and exit if directory +#------------------------------------------------------------------------------- + +if [ `is_disk $DST_PATH` -eq 0 ]; then + # Directory: delete checkpoint and directory + + unset i j XPATH_ELEMENTS + + while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" + done < <(onedatastore show -x $DS_ID | $XPATH \ + /DATASTORE/TEMPLATE/SOURCE \ + /DATASTORE/TEMPLATE/CLONE \ + /DATASTORE/TEMPLATE/VITASTOR_CONF \ + /DATASTORE/TEMPLATE/IMAGE_PREFIX \ + /DATASTORE/TEMPLATE/POOL_NAME) + + SRC="${XPATH_ELEMENTS[j++]}" + CLONE="${XPATH_ELEMENTS[j++]}" + VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" + IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" + POOL_NAME="${XPATH_ELEMENTS[j++]}" + + CLI=vitastor-cli + if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" + fi + + SRC_CHECKPOINT="${IMAGE_PREFIX}one-sys-${VM_ID}-checkpoint" + + ssh_exec_and_log "$DST_HOST" "$CLI rm $SRC_CHECKPOINT 2>/dev/null || exit 0" \ + "Error deleting $SRC_CHECKPOINT in $DST_HOST" + + log "Deleting $DST_PATH" + ssh_exec_and_log "$DST_HOST" "rm -rf $DST_PATH" "Error deleting $DST_PATH" + exit 0 +fi + +#------------------------------------------------------------------------------- +# Get Image information +#------------------------------------------------------------------------------- + +DISK_ID=$(echo "$DST_PATH" | $AWK -F. '{print $NF}') + +# Reads the disk parameters -- taken from image datastore +unset i j XPATH_ELEMENTS +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onevm show -x $VM_ID | $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/VITASTOR_CONF \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_PREFIX \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/POOL_NAME) + +SRC="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" +POOL_NAME="${XPATH_ELEMENTS[j++]}" + +if is_undeployed "$VM_ID" "$DST_HOST"; then + # get BRIDGE_LIST from datastore + XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + IFS= read -r -d '' BRIDGE_LIST < <(onedatastore show -x "$DS_ID" \ + | $XPATH /DATASTORE/TEMPLATE/BRIDGE_LIST ) + + if [ -n "$BRIDGE_LIST" ]; then + DST_HOST=$(get_destination_host) + fi +fi + +# No need to delete not cloned images +if [ "$CLONE" = "NO" ]; then + exit 0 +fi + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi + +if [ -n "$SRC" ]; then + # cloned, so the name will be "one---" + SRC_IMAGE="${SRC}-${VM_ID}-${DISK_ID}" +else + # volatile + SRC_IMAGE="${IMAGE_PREFIX}one-sys-${VM_ID}-${DISK_ID}" +fi + +# Delete the image + +log "Deleting $DST_PATH" + +DELETE_CMD=$(cat <(.tm_mad_system) tm_mad fe:SOURCE host:remote_system_ds/disk.i vmid dsid + +# LN = Attach disk to a VM (Vitastor doesn't need to do anything in this case) + +SRC=$1 +DST=$2 +VM_ID=$3 +DS_ID=$4 + +exit 0 diff --git a/opennebula/remotes/tm/vitastor/mkimage b/opennebula/remotes/tm/vitastor/mkimage new file mode 100755 index 00000000..13e59423 --- /dev/null +++ b/opennebula/remotes/tm/vitastor/mkimage @@ -0,0 +1,120 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# mkimage size format host:remote_system_ds/disk.i vmid dsid +# - size in MB of the image +# - format for the image +# - host is the target host to deploy the VM +# - remote_system_ds is the path for the system datastore in the host +# - vmid is the id of the VM +# - dsid is the target datastore (0 is the system datastore) + +SIZE=$1 +FORMAT=$2 +DST=$3 + +VMID=$4 +DSID=$5 + +#------------------------------------------------------------------------------- + +if [ -z "${ONE_LOCATION}" ]; then + TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh + LIB_LOCATION=/usr/lib/one +else + TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh + LIB_LOCATION=$ONE_LOCATION/lib +fi + +DRIVER_PATH=$(dirname $0) + +source $TMCOMMON + +source ${DRIVER_PATH}/../../etc/datastore/datastore.conf +source ${DRIVER_PATH}/../../datastore/libfs.sh + +#------------------------------------------------------------------------------- +# Set dst path and dir +#------------------------------------------------------------------------------- + +DST_PATH=`arg_path $DST` +DST_HOST=`arg_host $DST` +DST_DIR=`dirname $DST_PATH` + +DISK_ID=$(echo $DST|awk -F. '{print $NF}') + +#------------------------------------------------------------------------------- +# Get Image information +#------------------------------------------------------------------------------- + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onevm show -x $VMID | $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/VITASTOR_CONF \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/POOL_NAME \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_PREFIX \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/FS) + +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +POOL_NAME="${XPATH_ELEMENTS[j++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" +FS="${XPATH_ELEMENTS[j++]}" + +CLI= +QEMU_ARG="" +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" + QEMU_ARG=":config_path=${VITASTOR_CONF}" +fi + +IMAGE_NAME="${IMAGE_PREFIX}one-sys-${VMID}-${DISK_ID}" + +ssh_make_path $DST_HOST $DST_DIR + +set -e -o pipefail + +# if user requested a swap or specifies a FS, we need to create a local +# formatted image and upload into existing Vitastor image +FS_OPTS=$(eval $(echo "echo \$FS_OPTS_$FS")) + +MKIMAGE_CMD=$(cat < vm_xml) + +base_path = ENV['BACKUP_BASE_PATH'] + +bck_dir = if base_path + "#{base_path}/#{vmid}/backup" + else + "#{rdir}/backup" + end + +snap_cmd = '' +expo_cmd = '' +clup_cmd = '' + +vm.elements.each 'TEMPLATE/DISK' do |d| + did = d.elements['DISK_ID'].text + next unless disks.include? did + + src = d.elements['SOURCE'].text + clon = d.elements['CLONE'].text + + src_image = if clon == 'NO' then src else "#{src}-#{vmid}-#{did}" end + + cmd = 'vitastor-cli' + qemu_arg = '' + if d.elements['VITASTOR_CONF'] + cmd = cmd + ' --config_path ' + d.elements['VITASTOR_CONF'] + qemu_arg += 'config_path='+d.elements['VITASTOR_CONF']+':' + end + + draw = "#{bck_dir}/disk.#{did}.raw" + ddst = "#{bck_dir}/disk.#{did}.0" + + expo_cmd << ds.cmd_confinement("qemu-img convert -m 4 -O qcow2 'vitastor:#{qemu_arg}image=#{src_image}' #{ddst}\n", rdir) + + clup_cmd << "rm -f #{draw}\n" +rescue StandardError => e + STDERR.puts "Missing configuration attributes in DISK: #{e.message}" + exit(1) +end + +if LIVE + freeze, thaw = fsfreeze(vm, deploy_id) +end + +script = <<~EOS + set -ex -o pipefail + + # Prepare backup folder + [ -d #{bck_dir} ] && rm -rf #{bck_dir} + + mkdir -p #{bck_dir} + + echo "#{Base64.encode64(vm_xml)}" > #{bck_dir}/vm.xml + + #{freeze} + + #{snap_cmd} + + #{thaw} + + #{expo_cmd} + + #{clup_cmd} +EOS + +rc = TransferManager::Action.ssh('prebackup_live', + :host => rhost, + :cmds => script, + :nostdout => false, + :nostderr => false +) + +if rc.code != 0 + STDERR.puts "Error preparing disk files: #{rc.stdout} #{rc.stderr}" +end + +exit(rc.code) diff --git a/opennebula/remotes/tm/vitastor/prebackup_live b/opennebula/remotes/tm/vitastor/prebackup_live new file mode 100755 index 00000000..12e2448c --- /dev/null +++ b/opennebula/remotes/tm/vitastor/prebackup_live @@ -0,0 +1,8 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +export LIVE=1 +`dirname $0`/prebackup $@ diff --git a/opennebula/remotes/tm/vitastor/premigrate b/opennebula/remotes/tm/vitastor/premigrate new file mode 120000 index 00000000..836a1cd2 --- /dev/null +++ b/opennebula/remotes/tm/vitastor/premigrate @@ -0,0 +1 @@ +../ceph/premigrate \ No newline at end of file diff --git a/opennebula/remotes/tm/vitastor/resize b/opennebula/remotes/tm/vitastor/resize new file mode 100755 index 00000000..f7a9d01a --- /dev/null +++ b/opennebula/remotes/tm/vitastor/resize @@ -0,0 +1,81 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# resize image size vmid + +SRC=$1 +SIZE=$2 +VM_ID=$3 + +#-------------------------------------------------------------------------------- + +if [ -z "${ONE_LOCATION}" ]; then + TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh + LIB_LOCATION=/usr/lib/one +else + TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh + LIB_LOCATION=$ONE_LOCATION/lib +fi + +DRIVER_PATH=$(dirname $0) + +source $TMCOMMON + +#------------------------------------------------------------------------------- +# Set dst path and dir +#------------------------------------------------------------------------------- + +SRC_HOST=`arg_host $SRC` +SRC_PATH=`arg_path $SRC` + +#------------------------------------------------------------------------------- +# Get Image information +#------------------------------------------------------------------------------- + +DISK_ID=$(echo "$SRC_PATH" | $AWK -F. '{print $NF}') + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onevm show -x $VM_ID | $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/VITASTOR_CONF \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/IMAGE_PREFIX \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/PERSISTENT) + +SRC_IMAGE="${XPATH_ELEMENTS[j++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" +PERSISTENT="${XPATH_ELEMENTS[j++]}" + +if [ -n "${SRC_IMAGE}" ]; then + if [ "${PERSISTENT}" != 'YES' ]; then + SRC_IMAGE="${SRC_IMAGE}-${VM_ID}-${DISK_ID}" + fi +else + SRC_IMAGE="${IMAGE_PREFIX}one-sys-${VM_ID}-${DISK_ID}" +fi + +#------------------------------------------------------------------------------- +# Resize disk +#------------------------------------------------------------------------------- + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi + +RESIZE_CMD=$(cat < 'restore', + :vm_id => vm_id) + # -------------------------------------------------------------------------- + # Image & Datastore information + # -------------------------------------------------------------------------- + bk_img = OpenNebula::Image.new_with_id(bk_img_id, action.one) + + rc = bk_img.info + raise rc.message.to_s if OpenNebula.is_error?(rc) + + bk_ds = TransferManager::Datastore.from_image_ds(:image => bk_img, + :client => action.one) + + # -------------------------------------------------------------------------- + # Backup information + # sample output: {"0":"rsync://100//0:3ffce7/var/lib/one/datastores/100/1/3ffce7/disk.0.0"} + # -------------------------------------------------------------------------- + xml_data = <<~EOS + #{action.vm.to_xml} + #{bk_img.to_xml} + EOS + + rc = bk_ds.action("ls -i #{inc_id}", xml_data) + + raise 'cannot list backup contents' unless rc.code == 0 + + disk_urls = JSON.parse(rc.stdout) + disk_urls = disk_urls.select {|id, _url| id.to_i == disk_id } if disk_id != -1 + + # -------------------------------------------------------------------------- + # Restore disk_urls in Host VM folder + # -------------------------------------------------------------------------- + vitastor_disks = get_vitastor_disks(action.vm.template_xml) + success_disks = [] + + info = {} + + disk_urls.each do |id, url| + vitastor_disk = vitastor_disks[id.to_i] + randsuffix = SecureRandom.hex(5) + + vitastor_one_ds = OpenNebula::Datastore.new_with_id( + action.vm["/VM/TEMPLATE/DISK[DISK_ID = #{id}]/DATASTORE_ID"].to_i, action.one + ) + vitastor_ds = TransferManager::Datastore.new(:ds => vitastor_one_ds, :client => action.one) + + src_image = vitastor_disk.elements['SOURCE'].text + disk_id = vitastor_disk.elements['DISK_ID'].text + if vitastor_disk.elements['CLONE'].text == 'YES' + src_image += '-'+vm_id+'-'+disk_id + end + + cli = 'vitastor-cli' + config_path = vitastor_disk.elements['VITASTOR_CONF'] + qemu_args = '' + if config_path: + cli += ' --config_path "'+config_path.text+'"' + qemu_args += ':config_path='+config_path.text + + info[vitastor_disk] = { + :br => vitastor_ds.pick_bridge, + :bak => "#{src_image}.backup.#{randsuffix}", + :old => "#{src_image}.old.#{randsuffix}", + :cli => cli, + :img => src_image, + } + + upload_vitastor = <<~EOS + set -e + tmpimg="$(mktemp -t disk#{id}.XXXX)" + #{__dir__}/../../datastore/downloader.sh --nodecomp #{url} $tmpimg + #{cli} create -s $(qemu-img info --output json $tmpimg | jq -r '.["virtual-size"]') #{info[vitastor_disk][:bak]} + qemu-img convert -m 4 -O raw $tmpimg "vitastor:image=#{info[vitastor_disk][:bak]}#{qemu_args}" + rm -f $tmpimg + EOS + + rc = action.ssh(:host => info[vitastor_disk][:br], + :cmds => upload_ceph, + :forward => false, + :nostdout => false, + :nostderr => false) + + break if rc.code != 0 + + success_disks << vitastor_disk + end + + # Rollback and raise error if it was unable to backup all disks + if success_disks.length != disk_urls.length + success_disks.each do |vitastor_disk| + cleanup = <<~EOS + #{info[vitastor_disk][:cli]} rm #{info[vitastor_disk][:bak]} + EOS + + action.ssh(:host => info[vitastor_disk][:br], + :cmds => cleanup, + :forward => false, + :nostdout => false, + :nostderr => false) + end + + raise "error uploading backup disk to Vitastor (#{success_disks.length}/#{disk_urls.length})" + end + + # -------------------------------------------------------------------------- + # Replace VM disk_urls with backup copies (~prolog) + # -------------------------------------------------------------------------- + success_disks.each do |vitastor_disk| + move = <<~EOS + set -e + #{info[vitastor_disk][:cli]} mv #{info[vitastor_disk][:img]} #{info[vitastor_disk][:old]}; + #{info[vitastor_disk][:cli]} mv #{info[vitastor_disk][:bak]} #{info[vitastor_disk][:img]}; + for i in $(#{info[vitastor_disk][:cli]} ls --json "#{info[vitastor_disk][:img]}@*"); do + #{info[vitastor_disk][:cli]} rm "$i" + done + #{info[vitastor_disk][:cli]} rm #{info[vitastor_disk][:old]} + EOS + + rc = action.ssh(:host => info[vitastor_disk][:br], + :cmds => move, + :forward => false, + :nostdout => false, + :nostderr => false) + + warn 'cannot restore disk backup' if rc.code != 0 + end +rescue StandardError => e + STDERR.puts "Error restoring VM disks: #{e.message}" + exit(1) +end diff --git a/opennebula/remotes/tm/vitastor/snap_create b/opennebula/remotes/tm/vitastor/snap_create new file mode 100755 index 00000000..f4d20df2 --- /dev/null +++ b/opennebula/remotes/tm/vitastor/snap_create @@ -0,0 +1,78 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +# snap_create host:parent_image snap_id vmid ds_id + +SRC=$1 +SNAP_ID=$2 +VM_ID=$3 +DS_ID=$4 + +#-------------------------------------------------------------------------------- + +if [ -z "${ONE_LOCATION}" ]; then + TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh + LIB_LOCATION=/usr/lib/one +else + TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh + LIB_LOCATION=$ONE_LOCATION/lib +fi + +DRIVER_PATH=$(dirname $0) + +source $TMCOMMON + +#------------------------------------------------------------------------------- +# Set dst path and dir +#------------------------------------------------------------------------------- + +SRC_HOST=`arg_host $SRC` +SRC_PATH=`arg_path $SRC` + +#------------------------------------------------------------------------------- +# Get Image information +#------------------------------------------------------------------------------- + +DISK_ID=$(echo "$SRC_PATH" | $AWK -F. '{print $NF}') + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <(onevm show -x $VM_ID | $XPATH \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/SOURCE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/CLONE \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/VITASTOR_CONF \ + /VM/TEMPLATE/DISK[DISK_ID=$DISK_ID]/TYPE ) + +SRC_IMAGE="${XPATH_ELEMENTS[j++]}" +CLONE="${XPATH_ELEMENTS[j++]}" +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +TYPE="${XPATH_ELEMENTS[j++]}" + +if [ "$CLONE" = "YES" ]; then + SRC_IMAGE="${SRC_IMAGE}-${VM_ID}-${DISK_ID}" +fi + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi + +#------------------------------------------------------------------------------- +# Create snapshots +#------------------------------------------------------------------------------- + +SNAP_CREATE_CMD=$(cat < 0' + $CLI rm "$SRC_IMAGE" || true + $CLI create --parent "$SRC_IMAGE@$SNAP_ID" "$SRC_IMAGE" +EOF +) + +ssh_exec_and_log "$SRC_HOST" "$SNAP_REVERT_CMD" "Error reverting snapshot $SNAP_ID for $SRC_IMAGE" diff --git a/opennebula/remotes/vmm/kvm/deploy.vitastor b/opennebula/remotes/vmm/kvm/deploy.vitastor new file mode 100755 index 00000000..dbb27654 --- /dev/null +++ b/opennebula/remotes/vmm/kvm/deploy.vitastor @@ -0,0 +1,15 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +DRIVER_PATH=$(dirname $0) +DEP_FILE=$1 +DEP_FILE_LOCATION=$(dirname $DEP_FILE) + +cat > $DEP_FILE + +python3 $DRIVER_PATH/deploy_vitastor.py $DEP_FILE $DEP_FILE_LOCATION/vm.xml + +cat $DEP_FILE | $DRIVER_PATH/deploy $@ diff --git a/opennebula/remotes/vmm/kvm/deploy_vitastor.py b/opennebula/remotes/vmm/kvm/deploy_vitastor.py new file mode 100755 index 00000000..d98285df --- /dev/null +++ b/opennebula/remotes/vmm/kvm/deploy_vitastor.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +import base64 +from sys import argv, stderr +from xml.etree import ElementTree as ET + +dep_file = argv[1] +with open(dep_file, 'rb') as fd: + dep_txt = base64.b64decode(fd.read()) +dep = ET.fromstring(dep_txt) + +vm_file = argv[2] +with open(vm_file, 'rb') as fd: + vm_txt = base64.b64decode(fd.read()) +vm = ET.fromstring(vm_txt) + +ET.register_namespace('qemu', 'http://libvirt.org/schemas/domain/qemu/1.0') +ET.register_namespace('one', 'http://opennebula.org/xmlns/libvirt/1.0') + +vm_id = vm.find('./ID').text +context_disk_id = vm.find('./TEMPLATE/CONTEXT/DISK_ID').text +changed = 0 +txt = lambda x: '' if x is None else x.text + +for disk in dep.findall('./devices/disk[@type="file"]'): + try: + disk_id = disk.find('./source').attrib['file'].split('.')[-1] + vm_disk = vm.find('./TEMPLATE/DISK[DISK_ID="{}"]'.format(disk_id)) + if vm_disk is None: + continue + tm_mad = txt(vm_disk.find('./TM_MAD')) + if tm_mad != 'vitastor': + continue + src_image = txt(vm_disk.find('./SOURCE')) + clone = txt(vm_disk.find('./CLONE')) + vitastor_conf = txt(vm_disk.find('./VITASTOR_CONF')) + if clone == "YES": + src_image += "-"+vm_id+"-"+disk_id + # modify + changed = 1 + disk.attrib['type'] = 'network' + disk.remove(disk.find('./source')) + src = ET.SubElement(disk, 'source') + src.attrib['protocol'] = 'vitastor' + src.attrib['name'] = src_image + if vitastor_conf: + # path to config should be added to /etc/apparmor.d/local/abstractions/libvirt-qemu + config = ET.SubElement(src, 'config') + config.text = vitastor_conf + except Exception as e: + print("Error: {}".format(e), file=stderr) + +if changed: + ET.ElementTree(dep).write(dep_file) diff --git a/opennebula/remotes/vmm/kvm/restore.vitastor b/opennebula/remotes/vmm/kvm/restore.vitastor new file mode 100755 index 00000000..44708f19 --- /dev/null +++ b/opennebula/remotes/vmm/kvm/restore.vitastor @@ -0,0 +1,37 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +DRIVER_PATH=$(dirname $0) + +source $DRIVER_PATH/../../etc/vmm/kvm/kvmrc +source $DRIVER_PATH/../../scripts_common.sh + +FILE=$1 +HOST=$2 +DEPLOY_ID=$3 +VM_ID=$4 +DS_ID=$5 + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /VMM_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF \ + /VMM_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IMAGE_PREFIX) + +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path $VITASTOR_CONF" +fi + +SRC_IMAGE="${IMAGE_PREFIX}one-sys-${VM_ID}-checkpoint" + +exec_and_log "$CLI dd iimg=$SRC_IMAGE of=$FILE" "Error exporting checkpoint into from $SRC_IMAGE to $FILE" +exec_and_log "$CLI rm $SRC_IMAGE" "Error removing checkpoint $SRC_IMAGE" diff --git a/opennebula/remotes/vmm/kvm/save.vitastor b/opennebula/remotes/vmm/kvm/save.vitastor new file mode 100755 index 00000000..fb26b8ef --- /dev/null +++ b/opennebula/remotes/vmm/kvm/save.vitastor @@ -0,0 +1,45 @@ +#!/bin/bash + +# Vitastor OpenNebula driver +# Copyright (c) Vitaliy Filippov, 2024+ +# License: Apache-2.0 http://www.apache.org/licenses/LICENSE-2.0 + +DRIVER_PATH=$(dirname $0) + +source $DRIVER_PATH/../../etc/vmm/kvm/kvmrc +source $DRIVER_PATH/../../scripts_common.sh + +DEPLOY_ID=$1 +FILE=$2 +VM_ID=$4 +DS_ID=$5 + +XPATH="${DRIVER_PATH}/../../datastore/xpath.rb --stdin" + +unset i j XPATH_ELEMENTS + +while IFS= read -r -d '' element; do + XPATH_ELEMENTS[i++]="$element" +done < <($XPATH \ + /VMM_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/VITASTOR_CONF \ + /VMM_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/IMAGE_PREFIX \ + /VMM_DRIVER_ACTION_DATA/DATASTORE/TEMPLATE/POOL_NAME) + +VITASTOR_CONF="${XPATH_ELEMENTS[j++]}" +IMAGE_PREFIX="${XPATH_ELEMENTS[j++]}" +POOL_NAME="${XPATH_ELEMENTS[j++]}" + +CLI=vitastor-cli +if [ -n "$VITASTOR_CONF" ]; then + CLI="$CLI --config_path ${VITASTOR_CONF}" +fi +if [ -n "$POOL_NAME" ]; then + CLI="$CLI --pool ${POOL_NAME}" +fi + +DST_IMAGE="${IMAGE_PREFIX}one-sys-${VM_ID}-checkpoint" + +exec_and_log "$CLI dd if=$FILE oimg=$DST_IMAGE" "Error importing checkpoint into $DST_IMAGE" +exec_and_log "$RM -f $FILE" "Error removing checkpoint ($FILE)" + +exit 0 diff --git a/opennebula/sudoers.d/opennebula-vitastor b/opennebula/sudoers.d/opennebula-vitastor new file mode 100644 index 00000000..70d8bbfa --- /dev/null +++ b/opennebula/sudoers.d/opennebula-vitastor @@ -0,0 +1 @@ +oneadmin ALL=(ALL) NOPASSWD: /usr/bin/vitastor-nbd diff --git a/opennebula/vitastor-imageds.conf b/opennebula/vitastor-imageds.conf new file mode 100644 index 00000000..72bb9590 --- /dev/null +++ b/opennebula/vitastor-imageds.conf @@ -0,0 +1,9 @@ +NAME = "Vitastor Images" +DS_MAD = "vitastor" +TM_MAD = "vitastor" +TYPE = "IMAGE_DS" +DISK_TYPE = "file" +BRIDGE_LIST = "opennebula1" +POOL_NAME = "test1" +IMAGE_PREFIX = "my" +STAGING_DIR = /var/tmp diff --git a/opennebula/vitastor-systemds.conf b/opennebula/vitastor-systemds.conf new file mode 100644 index 00000000..71260c16 --- /dev/null +++ b/opennebula/vitastor-systemds.conf @@ -0,0 +1,6 @@ +NAME = "Vitastor System" +TM_MAD = "vitastor" +TYPE = "SYSTEM_DS" +BRIDGE_LIST = "opennebula1" +POOL_NAME = "test1" +IMAGE_PREFIX = "my" diff --git a/rpm/vitastor-el7.Dockerfile b/rpm/vitastor-el7.Dockerfile index 92f4314e..f7358b7b 100644 --- a/rpm/vitastor-el7.Dockerfile +++ b/rpm/vitastor-el7.Dockerfile @@ -43,5 +43,5 @@ RUN set -e; \ rpmbuild -ba vitastor.spec; \ mkdir -p /root/packages/vitastor-el7; \ rm -rf /root/packages/vitastor-el7/*; \ - cp ~/rpmbuild/RPMS/*/vitastor* /root/packages/vitastor-el7/; \ + cp ~/rpmbuild/RPMS/*/*vitastor* /root/packages/vitastor-el7/; \ cp ~/rpmbuild/SRPMS/vitastor* /root/packages/vitastor-el7/ diff --git a/rpm/vitastor-el7.spec b/rpm/vitastor-el7.spec index 31b0edcf..8b971c64 100644 --- a/rpm/vitastor-el7.spec +++ b/rpm/vitastor-el7.spec @@ -89,6 +89,19 @@ Requires: fio = 3.7-1.el7 Vitastor fio drivers for benchmarking. +%package -n opennebula-vitastor +Summary: Vitastor for OpenNebula +Group: Development/Libraries +Requires: vitastor-client +Requires: jq +Requires: python3-lxml +Requires: patch + + +%description -n opennebula-vitastor +Vitastor storage plugin for OpenNebula. + + %prep %setup -q @@ -113,6 +126,11 @@ mkdir -p %buildroot/lib/systemd/system cp mon/scripts/vitastor.target mon/scripts/vitastor-mon.service mon/scripts/vitastor-osd@.service %buildroot/lib/systemd/system mkdir -p %buildroot/lib/udev/rules.d cp mon/scripts/90-vitastor.rules %buildroot/lib/udev/rules.d +mkdir -p %buildroot/var/lib/one +cp -r opennebula/remotes %buildroot/var/lib/one +cp opennebula/install.sh %buildroot/var/lib/one/remotes/datastore/vitastor/ +mkdir -p %buildroot/etc/ +cp -r opennebula/sudoers.d %buildroot/etc/ %files @@ -173,4 +191,14 @@ chown vitastor:vitastor /var/lib/vitastor %_libdir/libfio_vitastor_sec.so +%files -n opennebula-vitastor +/var/lib/one +/etc/sudoers.d/opennebula-vitastor + + +%triggerin -n opennebula-vitastor -- opennebula +[ $2 = 0 ] || exit 0 +/var/lib/one/remotes/datastore/vitastor/install.sh + + %changelog diff --git a/rpm/vitastor-el8.Dockerfile b/rpm/vitastor-el8.Dockerfile index 5bb6bf37..017c9313 100644 --- a/rpm/vitastor-el8.Dockerfile +++ b/rpm/vitastor-el8.Dockerfile @@ -42,5 +42,5 @@ RUN set -e; \ rpmbuild -ba vitastor.spec; \ mkdir -p /root/packages/vitastor-el8; \ rm -rf /root/packages/vitastor-el8/*; \ - cp ~/rpmbuild/RPMS/*/vitastor* /root/packages/vitastor-el8/; \ + cp ~/rpmbuild/RPMS/*/*vitastor* /root/packages/vitastor-el8/; \ cp ~/rpmbuild/SRPMS/vitastor* /root/packages/vitastor-el8/ diff --git a/rpm/vitastor-el8.spec b/rpm/vitastor-el8.spec index c9ccff5c..f6dc559d 100644 --- a/rpm/vitastor-el8.spec +++ b/rpm/vitastor-el8.spec @@ -87,6 +87,19 @@ Requires: fio = 3.7-3.el8 Vitastor fio drivers for benchmarking. +%package -n opennebula-vitastor +Summary: Vitastor for OpenNebula +Group: Development/Libraries +Requires: vitastor-client +Requires: jq +Requires: python3-lxml +Requires: patch + + +%description -n opennebula-vitastor +Vitastor storage plugin for OpenNebula. + + %prep %setup -q @@ -110,6 +123,11 @@ mkdir -p %buildroot/lib/systemd/system cp mon/scripts/vitastor.target mon/scripts/vitastor-mon.service mon/scripts/vitastor-osd@.service %buildroot/lib/systemd/system mkdir -p %buildroot/lib/udev/rules.d cp mon/scripts/90-vitastor.rules %buildroot/lib/udev/rules.d +mkdir -p %buildroot/var/lib/one +cp -r opennebula/remotes %buildroot/var/lib/one +cp opennebula/install.sh %buildroot/var/lib/one/remotes/datastore/vitastor/ +mkdir -p %buildroot/etc/ +cp -r opennebula/sudoers.d %buildroot/etc/ %files @@ -170,4 +188,14 @@ chown vitastor:vitastor /var/lib/vitastor %_libdir/libfio_vitastor_sec.so +%files -n opennebula-vitastor +/var/lib/one +/etc/sudoers.d/opennebula-vitastor + + +%triggerin -n opennebula-vitastor -- opennebula +[ $2 = 0 ] || exit 0 +/var/lib/one/remotes/datastore/vitastor/install.sh + + %changelog diff --git a/rpm/vitastor-el9.Dockerfile b/rpm/vitastor-el9.Dockerfile index b91ad4c5..7106ded5 100644 --- a/rpm/vitastor-el9.Dockerfile +++ b/rpm/vitastor-el9.Dockerfile @@ -25,5 +25,5 @@ RUN set -e; \ rpmbuild -ba vitastor.spec; \ mkdir -p /root/packages/vitastor-el9; \ rm -rf /root/packages/vitastor-el9/*; \ - cp ~/rpmbuild/RPMS/*/vitastor* /root/packages/vitastor-el9/; \ + cp ~/rpmbuild/RPMS/*/*vitastor* /root/packages/vitastor-el9/; \ cp ~/rpmbuild/SRPMS/vitastor* /root/packages/vitastor-el9/ diff --git a/rpm/vitastor-el9.spec b/rpm/vitastor-el9.spec index 3f22a4ce..49266e08 100644 --- a/rpm/vitastor-el9.spec +++ b/rpm/vitastor-el9.spec @@ -81,6 +81,19 @@ Requires: fio = 3.27-8.el9 Vitastor fio drivers for benchmarking. +%package -n opennebula-vitastor +Summary: Vitastor for OpenNebula +Group: Development/Libraries +Requires: vitastor-client +Requires: jq +Requires: python3-lxml +Requires: patch + + +%description -n opennebula-vitastor +Vitastor storage plugin for OpenNebula. + + %prep %setup -q @@ -103,6 +116,11 @@ mkdir -p %buildroot/lib/systemd/system cp mon/scripts/vitastor.target mon/scripts/vitastor-mon.service mon/scripts/vitastor-osd@.service %buildroot/lib/systemd/system mkdir -p %buildroot/lib/udev/rules.d cp mon/scripts/90-vitastor.rules %buildroot/lib/udev/rules.d +mkdir -p %buildroot/var/lib/one +cp -r opennebula/remotes %buildroot/var/lib/one +cp opennebula/install.sh %buildroot/var/lib/one/remotes/datastore/vitastor/ +mkdir -p %buildroot/etc/ +cp -r opennebula/sudoers.d %buildroot/etc/ %files @@ -163,4 +181,14 @@ chown vitastor:vitastor /var/lib/vitastor %_libdir/libfio_vitastor_sec.so +%files -n opennebula-vitastor +/var/lib/one +/etc/sudoers.d/opennebula-vitastor + + +%triggerin -n opennebula-vitastor -- opennebula +[ $2 = 0 ] || exit 0 +/var/lib/one/remotes/datastore/vitastor/install.sh + + %changelog