1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-25 02:50:08 +03:00

F OpenNebula/one#6265: Delete of snapshots in the middle of the chain

- Add suport for delete snapshots in the middle of the chain in qcow2
  (ALLOW_ORPHANS=NO). Example, snapshot 2 below:
                     0 <- 1 <- 2 <- 3

- It supports removing the last snapshot and active (blockpull) and
  first one (snapshot 0). The first snapshot cannot be deleted in
  persistent images because of the underlying link setting.

- Snapshots are deleted by blockcommit. For example to delete 2, 3 is
  committed into 2:
                    0 <- 1 <- 2: (2+3)
  A new file 3.current is created in the filesystem to look for the
  actual file name supporting snapshot 3.

- qcow2/shared operations snap_delete, snap_revert and cpds
  (disk-saveas) has been updated to react to the .current files

- The kvm/deploy action has been updated to resolve links in source file
  attributes. This is needed so the <backingStore> of the libvirt domain
  does not contain the sysmlinks that changes on the revert/create
  operations.

- The kvm/migrate action also includes a minor modification to resolv
  the VM disk

- Snapshots with more than 1 child (or their relatives) cannot be deleted to not
  break backing chains.

- It has been found that the snap-delete operation in suspend state is
  insecure as the guest caches may not be updated accordingly. The
  operation is now not allowed in this state.

- The associated state has been removed from oned. It needs to be
  removed in CLI/Sunstone and APIs

- The changes are for VM disk-snapshots. A similar update can be made
  for images in the datastore/fs snap_delete action. This operation is
  not implemented in this commit.

Note: Output of xmllint may or may not include new lines when multiple matches
of an xpath expression occurs. Parsing of xmllint output adds additional new lines
to deal with different versions.
This commit is contained in:
Ruben S. Montero 2023-07-20 09:51:15 +02:00
parent 1606a1c53e
commit 156cd04ba5
No known key found for this signature in database
GPG Key ID: A0CEA6FA880A1D87
24 changed files with 639 additions and 186 deletions

View File

@ -139,12 +139,16 @@ public:
int create_snapshot(const std::string& name, long long size_mb);
/**
* Check if an snapshot can be deleted (no children, no active)
* Check if an snapshot can be deleted, for the snap_delete operation on
* VMs
* @param id of the snapshot
* @param error if any
* @return true if can be deleted, false otherwise
*/
bool test_delete(int id, std::string& error) const;
bool test_delete(int id, bool persistent, std::string& error) const;
// Version for the snap_delete operation on images
bool test_delete_image(int id, std::string& error) const;
/**
* Removes the snapshot from the list
@ -173,8 +177,8 @@ public:
*/
void clear()
{
active = -1;
disk_id = -1;
active = -1;
_disk_id = -1;
snapshot_template.clear();
snapshot_pool.clear();
@ -183,15 +187,25 @@ public:
/**
* Return the disk_id of the snapshot list
*/
int get_disk_id() const
int disk_id() const
{
return disk_id;
return _disk_id;
}
/**
* Sets the disk id for this snapshot list
* @param did the id
*/
void set_disk_id(int did)
{
_disk_id = did;
snapshot_template.replace("DISK_ID", did);
};
/**
* Return the active snapshot id
*/
int get_active_id() const
int active_id() const
{
return active;
}
@ -201,20 +215,10 @@ public:
*/
void clear_disk_id()
{
disk_id = -1;
_disk_id = -1;
snapshot_template.erase("DISK_ID");
};
/**
* Sets the disk id for this snapshot list
* @param did the id
*/
void set_disk_id(int did)
{
disk_id = did;
snapshot_template.replace("DISK_ID", did);
};
/**
* @return number of snapshots in the list
*/
@ -223,6 +227,11 @@ public:
return snapshot_pool.size();
};
unsigned int next() const
{
return next_snapshot;
}
/**
* @return true if snapshot_pool is empty
*/
@ -246,14 +255,25 @@ public:
/**
* @return total snapshot size (virtual) in mb
*/
long long get_total_size() const;
long long total_size() const;
/**
* Get the size (virtual) in mb of the given snapshot
* @param id of the snapshot
* @return size or 0 if not found
*/
long long get_snapshot_size(int id) const;
long long snapshot_size(int id) const;
/**
* Return Snapshot children
* @param if of the snapshot
* @param children the attribute string
* @return the number of children
* -1 No snapshot
* 0 CHILDREN not defined
* N number of children in "0,2,3,5" ---> 4
*/
int children(int id, std::string& children) const;
/**
* Get Attribute from the given snapshot
@ -262,7 +282,7 @@ public:
*
* @return value or empty if not found
*/
std::string get_snapshot_attribute(int id, const char* name) const;
std::string snapshot_attribute(int id, const char* name) const;
private:
@ -317,7 +337,7 @@ private:
/**
* Id of the disk associated with this snapshot list
*/
int disk_id;
int _disk_id;
/**
* Allow to remove parent snapshots and active one

View File

@ -126,7 +126,7 @@ public:
DISK_SNAPSHOT_REVERT_POWEROFF = 52,
DISK_SNAPSHOT_DELETE_POWEROFF = 53,
DISK_SNAPSHOT_SUSPENDED = 54,
DISK_SNAPSHOT_REVERT_SUSPENDED = 55,
//DISK_SNAPSHOT_REVERT_SUSPENDED = 55,
DISK_SNAPSHOT_DELETE_SUSPENDED = 56,
DISK_SNAPSHOT = 57,
//DISK_SNAPSHOT_REVERT = 58,

View File

@ -210,7 +210,7 @@ public:
return 0;
}
return snapshots->get_total_size();
return snapshots->total_size();
}
/**
@ -225,7 +225,7 @@ public:
return 0;
}
return snapshots->get_snapshot_size(snap_id);
return snapshots->snapshot_size(snap_id);
}
/**

View File

@ -2202,8 +2202,7 @@ int DispatchManager::disk_snapshot_revert(int vid, int did, int snap_id,
VirtualMachine::VmState state = vm->get_state();
VirtualMachine::LcmState lstate = vm->get_lcm_state();
if ((state !=VirtualMachine::POWEROFF || lstate !=VirtualMachine::LCM_INIT)&&
(state !=VirtualMachine::SUSPENDED|| lstate !=VirtualMachine::LCM_INIT))
if (state != VirtualMachine::POWEROFF || lstate != VirtualMachine::LCM_INIT)
{
oss << "Could not revert to disk snapshot for VM " << vid
<< ", wrong state " << vm->state_str() << ".";
@ -2236,20 +2235,8 @@ int DispatchManager::disk_snapshot_revert(int vid, int did, int snap_id,
close_cp_history(vmpool, vm.get(), VMActions::DISK_SNAPSHOT_REVERT_ACTION, ra);
switch (state)
{
case VirtualMachine::POWEROFF:
vm->set_state(VirtualMachine::ACTIVE);
vm->set_state(VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF);
break;
case VirtualMachine::SUSPENDED:
vm->set_state(VirtualMachine::ACTIVE);
vm->set_state(VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED);
break;
default: break;
}
vm->set_state(VirtualMachine::ACTIVE);
vm->set_state(VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF);
vmpool->update(vm.get());
@ -2305,7 +2292,14 @@ int DispatchManager::disk_snapshot_delete(int vid, int did, int snap_id,
return -1;
}
if (!snaps->test_delete(snap_id, error_str))
const VirtualMachineDisk * disk = vm->get_disk(did);
if (disk == nullptr)
{
return -1;
}
if (!snaps->test_delete(snap_id, disk->is_persistent(), error_str))
{
return -1;
}

View File

@ -39,7 +39,6 @@ void DispatchManager::trigger_suspend_success(int vid)
vm->get_lcm_state() == VirtualMachine::PROLOG_MIGRATE_SUSPEND ||
vm->get_lcm_state() == VirtualMachine::PROLOG_MIGRATE_SUSPEND_FAILURE||
vm->get_lcm_state() == VirtualMachine::DISK_SNAPSHOT_SUSPENDED ||
vm->get_lcm_state() == VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED||
vm->get_lcm_state() == VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED))
{
VirtualMachineTemplate quota_tmpl;

View File

@ -607,7 +607,7 @@ void Image::disk_attribute(VirtualMachineDisk * disk,
disk->replace("SIZE", size_mb);
}
snap_size = snapshots.get_total_size();
snap_size = snapshots.total_size();
disk->replace("DISK_SNAPSHOT_TOTAL_SIZE", snap_size);
// Force FORMAT and DRIVER from image

View File

@ -601,7 +601,7 @@ int ImageManager::delete_image(int iid, string& error_str)
int uid = img->get_uid();
int gid = img->get_gid();
long long size = img->get_size() + img->get_snapshots().get_total_size();
long long size = img->get_size() + img->get_snapshots().total_size();
img.reset();
@ -1200,7 +1200,7 @@ int ImageManager::delete_snapshot(int iid, int sid, string& error)
const Snapshots& snaps = img->get_snapshots();
if (!snaps.test_delete(sid, error))
if (!snaps.test_delete_image(sid, error))
{
return -1;
}

View File

@ -480,7 +480,7 @@ void ImageManager::_rm(unique_ptr<image_msg_t> msg)
gid = image->get_gid();
source = image->get_source();
size = image->get_size();
snap_size = (image->get_snapshots()).get_total_size();
snap_size = (image->get_snapshots()).total_size();
if ( image->get_type() == Image::BACKUP )
{
@ -680,7 +680,7 @@ void ImageManager::_snap_delete(unique_ptr<image_msg_t> msg)
ds_id = image->get_ds_id();
uid = image->get_uid();
gid = image->get_gid();
snap_size = (image->get_snapshots()).get_snapshot_size(snap_id);
snap_size = (image->get_snapshots()).snapshot_size(snap_id);
image->delete_snapshot(snap_id);
}
@ -795,7 +795,7 @@ void ImageManager::_snap_flatten(unique_ptr<image_msg_t> msg)
ds_id = image->get_ds_id();
uid = image->get_uid();
gid = image->get_gid();
snap_size = (image->get_snapshots()).get_total_size();
snap_size = (image->get_snapshots()).total_size();
image->clear_snapshots();
}

View File

@ -1135,7 +1135,6 @@ void LifeCycleManager::clean_up_vm(VirtualMachine * vm, bool dispose,
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE:
vm->clear_snapshot_disk();
@ -1509,7 +1508,6 @@ void LifeCycleManager::recover(VirtualMachine * vm, bool success,
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT:
case VirtualMachine::DISK_SNAPSHOT_DELETE:
@ -1747,7 +1745,6 @@ void LifeCycleManager::retry(VirtualMachine * vm)
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT:
case VirtualMachine::DISK_SNAPSHOT_DELETE:
@ -1867,7 +1864,6 @@ void LifeCycleManager::trigger_updatesg(int sgid)
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::HOTPLUG_SAVEAS_POWEROFF:
case VirtualMachine::HOTPLUG_SAVEAS_SUSPENDED:

View File

@ -1961,7 +1961,6 @@ void LifeCycleManager::trigger_disk_snapshot_success(int vid)
break;
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
vm->log("LCM", Log::INFO, "VM disk snapshot operation completed.");
vm->revert_disk_snapshot(disk_id, snap_id, true);
break;
@ -2046,7 +2045,6 @@ void LifeCycleManager::trigger_disk_snapshot_success(int vid)
break;
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
dm->trigger_suspend_success(vid);
break;
@ -2117,7 +2115,6 @@ void LifeCycleManager::trigger_disk_snapshot_failure(int vid)
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
vm->log("LCM", Log::ERROR, "VM disk snapshot operation failed.");
break;
@ -2191,7 +2188,6 @@ void LifeCycleManager::trigger_disk_snapshot_failure(int vid)
break;
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
dm->trigger_suspend_success(vid);
break;

View File

@ -94,7 +94,7 @@ unique_ptr<PoolObjectSQL> RequestManagerChown::get_and_quota(
auto tmpl = make_unique<Template>();
tmpl->add("DATASTORE", img->get_ds_id());
tmpl->add("SIZE",img->get_size()+img->get_snapshots().get_total_size());
tmpl->add("SIZE",img->get_size()+img->get_snapshots().total_size());
quota_map.insert(make_pair(Quotas::DATASTORE, move(tmpl)));
}

View File

@ -104,7 +104,6 @@ void TransferManager::_transfer(unique_ptr<transfer_msg_t> msg)
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE:
lcm->trigger_disk_snapshot_success(id);
@ -175,7 +174,6 @@ void TransferManager::_transfer(unique_ptr<transfer_msg_t> msg)
case VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF:
case VirtualMachine::DISK_SNAPSHOT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED:
case VirtualMachine::DISK_SNAPSHOT_DELETE:
lcm->trigger_disk_snapshot_failure(id);

View File

@ -98,7 +98,7 @@ if [ "${LCM_STATE}" = '26' ] && [ "${VM_MAD}" != "kvm" ]; then
fi
if [ "$FORMAT" = "qcow2" ]; then
CP_CMD="$QEMU_IMG convert $SRC_PATH -O qcow2 $DST_PATH"
CP_CMD="$QEMU_IMG convert \${SRC_PATH} -O qcow2 $DST_PATH"
else
CP_CMD="cp -f $SRC_PATH $DST_PATH"
fi
@ -131,10 +131,16 @@ fi
CPDS_CMD=$(cat <<EOF
set -e -o pipefail
SRC_READLN=\$($READLINK -f $SRC_PATH)
if [ -f "${SRC_PATH}.current" ]; then
SRC_PATH=\$(cat ${SRC_PATH}.current)
else
SRC_PATH="${SRC_PATH}"
fi
SRC_READLN=\$($READLINK -f \${SRC_PATH})
DST_READLN=\$($READLINK -f $DST_PATH)
if [ \( -L $SRC_PATH \) -a \( "\$SRC_READLN" = "\$DST_READLN" \) ] ; then
if [ \( -L \${SRC_PATH} \) -a \( "\$SRC_READLN" = "\$DST_READLN" \) ] ; then
echo "Not moving files to image repo, they are the same"
else
${FREEZE_CP_CMD}

View File

@ -92,7 +92,7 @@ if [ "${LCM_STATE}" = '26' ] && [ "${VM_MAD}" != "kvm" ]; then
fi
if [ "$FORMAT" = "qcow2" ]; then
CP_CMD="$QEMU_IMG convert $SRC_PATH -O qcow2 $DST_PATH"
CP_CMD="$QEMU_IMG convert \${SRC_PATH} -O qcow2 $DST_PATH"
else
CP_CMD="cp -f $SRC_PATH $DST_PATH"
fi
@ -124,10 +124,16 @@ fi
CPDS_CMD=$(cat <<EOF
set -e -o pipefail
SRC_READLN=\$($READLINK -f $SRC_PATH)
if [ -f "${SRC_PATH}.current" ]; then
SRC_PATH=\$(cat ${SRC_PATH}.current)
else
SRC_PATH="${SRC_PATH}"
fi
SRC_READLN=\$($READLINK -f \${SRC_PATH})
DST_READLN=\$($READLINK -f $DST_PATH)
if [ \( -L $SRC_PATH \) -a \( "\$SRC_READLN" = "\$DST_READLN" \) ] ; then
if [ \( -L \${SRC_PATH} \) -a \( "\$SRC_READLN" = "\$DST_READLN" \) ] ; then
echo "Not moving files to image repo, they are the same"
else
${FREEZE_CP_CMD}

View File

@ -77,7 +77,7 @@ mkdir -p "${SNAP_DIR}"
if [ ! -L "${SNAP_DIR}/${SNAP_NAME}" ]; then
# backing file traversar link
ln -s . "${SNAP_DIR}/${SNAP_NAME}" # backing file traversar link
ln -s . "${SNAP_DIR}/${SNAP_NAME}"
fi
SNAP="\$(ls ${SNAP_DIR} | grep '^[[:digit:]]*$' | sort -n | tail -n 1 || :)"

View File

@ -30,11 +30,225 @@ fi
DRIVER_PATH=$(dirname $0)
. $TMCOMMON
source $TMCOMMON
source ${DRIVER_PATH}/../../etc/vmm/kvm/kvmrc
SRC_PATH=$(arg_path $SRC)
SRC_HOST=$(arg_host $SRC)
# ------------------------------------------------------------------------------
# Backing File managment functions:
# - scan_backing_files creates an associative map with snap_id -> backing file
# - is_parent returns the child (if any) of a given snapshot (uses the map)
# - do_live checks if virsh or qemu-img should be use (a running VM can delete
# snapshots outside of current backingstore, e.g. because a revert)
# - delete removes the snapshot from the backing chain using qemu-img rebase
# virsh blockcommit or blockpull as needed.
# ------------------------------------------------------------------------------
scan_backing_files() {
local -n bfs=$1
local snap_dir=$2
for i in $(ls "${snap_dir}"); do
f=$(realpath -s "${snap_dir}/${i}")
[ ! -f ${f} ] && continue
[[ "${f}" =~ \.current ]] && continue
bf=$(qemu-img info -U --output json "${f}" | jq -r '."backing-filename"')
ky="${f##*/}"
if [ "${bf:0:1}" = "/" ] ; then
bfs["${ky}"]="${bf}"
elif [ "${bf}" = "null" ]; then
bfs["${ky}"]="null"
else
bfs["${ky}"]="${snap_dir}/${bf##*/}"
fi
done
}
is_parent() {
local -n bfs=$1
local snap=$(echo "$2/$3" | tr -s '/')
local child=""
for file in "${!bfs[@]}"; do
bfile=$(echo "${bfs[$file]}" | tr -s '/')
if [ "${snap}" = "${bfile}" -o "${snap}" = $(realpath ${bfile}) ]; then
child="${file}"
break
fi
done
echo "${child}"
}
do_live() {
local domid=$1
local state=$2
local snap_id=$3
local luri=$4
local found=""
local dbs=()
if [ "${state}" != "59" ]; then
echo "${found}"
return 0
fi
bs=$(virsh -c "${luri}" dumpxml "${domid}" | \
xmllint --xpath '//backingStore/source/@file' - | \
sed -e 's/file=/\n/g' | tr -d '"')
while IFS= read -r f; do
f=$(echo ${f} | sed 's/[[:space:]]*$//')
id="${f##*/}"
dbs+=("${id}")
if [ "${id}" = "${snap_id}" ]; then
found="${snap_id}"
fi
done <<< "${bs}"
echo "${found}"
}
get_current_path() {
local snap_dir=$1
local snap_id=$2
local current=""
for i in $(ls ${snap_dir}/*.current); do
current=$(cat "${i}")
#This snapshot represent another one (is in a .current file)
if [ "${current##*/}" = "${snap_id}" ]; then
echo "${i}"
return 0
fi
done
echo "${snap_dir}/${snap_id}.current"
}
delete_snapshot() {
declare -gA backing_files
local dpath=$1
local target=$2
local snap_dir=$3
local snap_id=$4
local vm_id=$5
local vm_st=$6
local active=$7
local luri=$8
local qemu=$9
scan_backing_files backing_files "${snap_dir}"
# --------------------------------------------------------------------------
# Re-define snap_id if current exists and set snap_path
# --------------------------------------------------------------------------
local current=""
local cmd=""
local rm_current=""
if [ -f "${snap_dir}/${snap_id}.current" ]; then
rm_current="rm ${snap_dir}/${snap_id}.current"
current=$(cat "${snap_dir}/${snap_id}.current")
snap_id="${current##*/}"
fi
local snap_path="${snap_dir}/${snap_id}"
# --------------------------------------------------------------------------
# Set base snapshot for the delete operation
# --------------------------------------------------------------------------
local base="${backing_files[$snap_id]}"
local vbase=""
local qbase=""
if [ "${base}" = "null" ]; then
vbase=""
qbase='-b ""'
else
vbase="--base ${base}"
qbase="-b ${base}"
fi
# --------------------------------------------------------------------------
# Set child snapshot & live mode for the delete operation
# --------------------------------------------------------------------------
local child=$(is_parent backing_files "${snap_dir}" "${snap_id}")
local live=$(do_live "${vm_id}" "${vm_st}" "${snap_id}" "${luri}")
if [ "${active}" = "YES" ]; then
# ----------------------------------------------------------------------
# Active snapshot (last known by OpenNebula). Example, delete n:
# n-1 <-- n <-- next
# n-1 <-- next
#
# next = next + n. Remove n
# ----------------------------------------------------------------------
if [ -n "${live}" ]; then
cmd="virsh -c ${luri} blockpull ${vm_id} ${target} ${vbase} --wait"
else
cmd="${qemu} rebase -q -U -F qcow2 ${qbase} ${dpath}"
fi
cmd="${cmd}; rm ${snap_path}"
elif [ -n "${child}" ]; then
# ----------------------------------------------------------------------
# Snapshot in the middle of a chain. Example, delete n:
# n-1 <-- n <-- n+1
# (live: commit n+1 into n) n-1 <-- n'
# (poff: rebase n+1 to n-1) n-1 <-- n+1
#
# (live) n' = n + (n+1). (n+1).current = n. Remove n+1
# (poff) Remove n
# ----------------------------------------------------------------------
if [ -n "${live}" ]; then
local current_path=$(get_current_path ${snap_dir} ${child})
cmd="virsh -c ${luri} blockcommit ${vm_id} ${target} --top ${child} \
--base ${snap_path} --wait"
cmd="${cmd}; echo "${snap_path}" > ${current_path}"
cmd="${cmd}; rm ${snap_dir}/${child}"
else
cmd="${qemu} rebase -q -U -F qcow2 ${qbase} ${snap_dir}/${child}"
cmd="${cmd}; rm ${snap_path}"
fi
else
# ----------------------------------------------------------------------
# Snapshot has no children and not active. Example, delete n+1:
# n-1 <-- n <-- n+1
# \_ n+2 <-- next
#
# Remove n+1
# ----------------------------------------------------------------------
if [ -f "${snap_path}.current" ]; then
cmd="rm ${current}"
else
cmd="rm ${snap_path}"
fi
fi
# Remove current pointer if exists
cmd="${cmd}; ${rm_current}"
echo "${cmd}"
}
#-------------------------------------------------------------------------------
# Get Image information
#-------------------------------------------------------------------------------
@ -49,14 +263,24 @@ while IFS= read -r -d '' element; do
XPATH_ELEMENTS[i++]="$element"
done < <(onevm show -x $VMID| $XPATH \
/VM/TEMPLATE/DISK\[DISK_ID=$DISK_ID\]/SOURCE \
/VM/TEMPLATE/DISK\[DISK_ID=$DISK_ID\]/CLONE)
/VM/TEMPLATE/DISK\[DISK_ID=$DISK_ID\]/CLONE \
/VM/TEMPLATE/DISK\[DISK_ID=$DISK_ID\]/TARGET \
/VM/TEMPLATE/DISK\[DISK_ID=$DISK_ID\]/FORMAT \
/VM/SNAPSHOTS/SNAPSHOT\[ID=$SNAP_ID\]/ACTIVE \
/VM/DEPLOY_ID \
/VM/LCM_STATE)
DISK_SRC="${XPATH_ELEMENTS[j++]}"
CLONE="${XPATH_ELEMENTS[j++]}"
TARGET="${XPATH_ELEMENTS[j++]}"
FORMAT="${XPATH_ELEMENTS[j++]}"
ACTIVE="${XPATH_ELEMENTS[j++]}"
DEPLOY_ID="${XPATH_ELEMENTS[j++]}"
LCM_STATE="${XPATH_ELEMENTS[j++]}"
SYSTEM_DS_PATH=$(dirname ${SRC_PATH})
if [ "$CLONE" = "YES" ]; then
if [ "$CLONE" = "YES" -o ${0: -4} = ".ssh" ]; then
DISK_PATH="${SYSTEM_DS_PATH}/disk.${DISK_ID}"
else
DISK_PATH=$DISK_SRC
@ -65,6 +289,38 @@ fi
SNAP_DIR="${DISK_PATH}.snap"
SNAP_PATH="${SNAP_DIR}/${SNAP_ID}"
ssh_exec_and_log "${SRC_HOST}" "rm ${SNAP_PATH}" \
DELETE_CMD=$(cat <<EOT
set -e -o pipefail
$(type scan_backing_files | grep -v 'is a function')
$(type is_parent | grep -v 'is a function')
$(type do_live | grep -v 'is a function')
$(type get_current_path | grep -v 'is a function')
$(type delete_snapshot | grep -v 'is a function')
# DISK_RDLN is the path of the active file
DISK_RDLN="\$(readlink ${SYSTEM_DS_PATH}/disk.${DISK_ID})"
if [ "\${DISK_RDLN:0:1}" != "/" ] ; then
DISK_RDLN="${SYSTEM_DS_PATH}/\${DISK_RDLN}"
fi
cmd=\$(delete_snapshot "\${DISK_RDLN}" "${TARGET}" "${SNAP_DIR}" "${SNAP_ID}" \
"${DEPLOY_ID}" "${LCM_STATE}" "${ACTIVE}" "${LIBVIRT_URI}" "${QEMU_IMG}")
eval "\${cmd}"
EOT
)
# For disks using raw format just remove the snaphost file
if [ "$FORMAT" = "raw" ]; then
DELETE_CMD="rm \"${SNAP_PATH}\""
fi
ssh_exec_and_log "${SRC_HOST}" "${DELETE_CMD}" \
"Error deleting snapshot ${SNAP_PATH}"

View File

@ -1,46 +0,0 @@
#!/bin/bash
# -------------------------------------------------------------------------- #
# Copyright 2002-2023, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
# snap_delete host:parent_image snap_id vmid ds_id
SRC=$1
SNAP_ID=$2
if [ -z "${ONE_LOCATION}" ]; then
TMCOMMON=/var/lib/one/remotes/tm/tm_common.sh
else
TMCOMMON=$ONE_LOCATION/var/remotes/tm/tm_common.sh
fi
. $TMCOMMON
SRC_PATH=$(arg_path $SRC)
SRC_HOST=$(arg_host $SRC)
DISK_ID=$(basename ${SRC} | cut -d. -f2)
SYSTEM_DS_PATH=$(dirname ${SRC_PATH})
DISK_PATH="${SYSTEM_DS_PATH}/disk.${DISK_ID}"
SNAP_DIR="${DISK_PATH}.snap"
SNAP_PATH="${SNAP_DIR}/${SNAP_ID}"
ssh_exec_and_log "${SRC_HOST}" "rm ${SNAP_PATH}" \
"Error deleting snapshot ${SNAP_PATH}"

View File

@ -0,0 +1 @@
snap_delete

View File

@ -69,15 +69,23 @@ else
fi
SNAP_PATH="${SNAP_DIR}/${SNAP_ID}"
SNAP_PATH_SHORT="${SNAP_DIR_SHORT}/${SNAP_ID}"
if [ "$FORMAT" = "qcow2" ]; then
SNAP_CMD=$(cat <<EOF
set -e -o pipefail
if [ -f "${SNAP_PATH}.current" ]; then
CURRENT_PATH=\$(cat "${SNAP_PATH}.current")
CURRENT_ID="\${CURRENT_PATH##*/}"
SNAP_PATH_SHORT="${SNAP_DIR_SHORT}/\${CURRENT_ID}"
else
SNAP_PATH_SHORT="${SNAP_DIR_SHORT}/${SNAP_ID}"
fi
cd "${SNAP_DIR}"
qemu-img create -f qcow2 -o backing_fmt=qcow2 \
-b "${SNAP_PATH_SHORT}" "\$(readlink -f ${DISK_PATH})"
-b "\${SNAP_PATH_SHORT}" "\$(readlink -f ${DISK_PATH})"
EOF
)
else

View File

@ -66,9 +66,18 @@ if [ "$FORMAT" = "qcow2" ]; then
SNAP_CMD=$(cat <<EOF
set -e -o pipefail
if [ -f "${SNAP_PATH}.current" ]; then
CURRENT_PATH=\$(cat "${SNAP_PATH}.current")
CURRENT_ID="\${CURRENT_PATH##*/}"
SNAP_PATH_SHORT="${SNAP_DIR_SHORT}/\${CURRENT_ID}"
else
SNAP_PATH_SHORT="${SNAP_DIR_SHORT}/${SNAP_ID}"
fi
cd "${SNAP_DIR}"
qemu-img create -f qcow2 -o backing_fmt=qcow2 \
-b "${SNAP_PATH_SHORT}" "\$(readlink -f ${DISK_PATH})"
-b "\${SNAP_PATH_SHORT}" "\$(readlink -f ${DISK_PATH})"
EOF
)
else

View File

@ -13,6 +13,7 @@
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
#include <algorithm>
#include "Snapshots.h"
#include "NebulaUtil.h"
@ -22,17 +23,17 @@ using namespace std;
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
Snapshots::Snapshots(int _disk_id, AllowOrphansMode _orphans):
Snapshots::Snapshots(int __disk_id, AllowOrphansMode _orphans):
snapshot_template(false,'=',"SNAPSHOTS"),
next_snapshot(0),
active(-1),
disk_id(_disk_id),
_disk_id(__disk_id),
orphans(_orphans),
current_base(-1)
{
if (_disk_id != -1)
if (__disk_id != -1)
{
snapshot_template.add("DISK_ID", _disk_id);
snapshot_template.add("DISK_ID", __disk_id);
}
snapshot_template.add("ALLOW_ORPHANS", allow_orphans_mode_to_str(_orphans));
@ -46,7 +47,7 @@ Snapshots::Snapshots(const Snapshots& s):
snapshot_template(s.snapshot_template),
next_snapshot(0),
active(-1),
disk_id(-1),
_disk_id(-1),
orphans(DENY),
current_base(-1)
{
@ -59,7 +60,7 @@ Snapshots& Snapshots::operator= (const Snapshots& s)
{
next_snapshot = s.next_snapshot;
active = s.active;
disk_id = s.disk_id;
_disk_id = s._disk_id;
orphans = s.orphans;
current_base = s.current_base;
@ -119,7 +120,7 @@ void Snapshots::init()
if (snapshot_template.get("DISK_ID", did))
{
disk_id = did;
_disk_id = did;
}
snapshot_template.get("NEXT_SNAPSHOT", next_snapshot);
@ -255,8 +256,6 @@ int Snapshots::add_child_deny(VectorAttribute *snapshot)
void Snapshots::delete_snapshot(int id)
{
int parent_id;
VectorAttribute * snapshot = get_snapshot(id);
if (snapshot == nullptr)
@ -264,37 +263,92 @@ void Snapshots::delete_snapshot(int id)
return;
}
if (orphans == DENY || orphans == MIXED)
switch(orphans)
{
snapshot->vector_value("PARENT", parent_id);
//Remove this snapshot from parent's children
if (parent_id != -1)
case DENY:
case MIXED:
{
set<int> child_set;
int parent_id;
// -----------------------------------------------------------------
// Remove this snapshot from parent's children
// -----------------------------------------------------------------
snapshot->vector_value("PARENT", parent_id);
if (parent_id == -1)
{
break;
}
VectorAttribute * parent = get_snapshot(parent_id);
if (parent != nullptr)
if (parent == nullptr)
{
string children = parent->vector_value("CHILDREN");
break;
}
one_util::split_unique(children, ',', child_set);
string children = parent->vector_value("CHILDREN");
child_set.erase(id);
set<int> child_set;
children = one_util::join(child_set.begin(), child_set.end(), ',');
one_util::split_unique(children, ',', child_set);
if (children.empty())
child_set.erase(id);
// -----------------------------------------------------------------
// Add snapshot child to parent's children
//
// Only DENY for in-line chain removal of snapshots (1 child)
// -----------------------------------------------------------------
string my_children = snapshot->vector_value("CHILDREN");
set<int> my_child_set;
one_util::split_unique(my_children, ',', my_child_set);
if ( my_child_set.size() == 1 )
{
int child_id = *my_child_set.begin();
child_set.insert(child_id);
VectorAttribute * child = get_snapshot(child_id);
if ( child != nullptr )
{
parent->remove("CHILDREN");
}
else
{
parent->replace("CHILDREN", children);
child->replace("PARENT", parent_id);
}
}
children = one_util::join(child_set.begin(), child_set.end(), ',');
if (children.empty())
{
parent->remove("CHILDREN");
}
else
{
parent->replace("CHILDREN", children);
}
// -----------------------------------------------------------------
// Set parent to ACTIVE if this snapshot id the active one
//
// Only DENY when deleting the active snapshot
// -----------------------------------------------------------------
if (active == id)
{
active = parent_id;
parent->replace("ACTIVE", true);
}
break;
}
case ALLOW:
case FORMAT: //At this point allow orpahn should be mapped to DENY/ALLOW
break;
}
snapshot_template.remove(snapshot);
@ -382,7 +436,7 @@ const VectorAttribute * Snapshots::get_snapshot(int id) const
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
string Snapshots::get_snapshot_attribute(int id, const char * name) const
string Snapshots::snapshot_attribute(int id, const char * name) const
{
const VectorAttribute * snapshot = get_snapshot(id);
@ -396,7 +450,7 @@ string Snapshots::get_snapshot_attribute(int id, const char * name) const
/* -------------------------------------------------------------------------- */
long long Snapshots::get_snapshot_size(int id) const
long long Snapshots::snapshot_size(int id) const
{
long long snap_size = 0;
@ -410,10 +464,39 @@ long long Snapshots::get_snapshot_size(int id) const
return snap_size;
}
/* -------------------------------------------------------------------------- */
static int children_count(const VectorAttribute *snap, std::string& children)
{
if (snap == nullptr)
{
return -1;
}
children.clear();
if (snap->vector_value("CHILDREN", children) == -1 || children.empty())
{
return 0;
}
return std::count(children.begin(), children.end(), ',') + 1;
}
int Snapshots::children(int id, std::string& children) const
{
if (id <= -1)
{
return -1;
}
return children_count(get_snapshot(id), children);
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
bool Snapshots::test_delete(int id, string& error) const
bool Snapshots::test_delete_image(int id, string& error) const
{
bool current;
string children;
@ -448,11 +531,111 @@ bool Snapshots::test_delete(int id, string& error) const
return true;
}
/* -------------------------------------------------------------------------- */
bool Snapshots::test_delete(int id, bool persistent, string& error) const
{
string childs;
int ccount, p_ccount, c_ccount, p_id, c_id;
const VectorAttribute * snapshot = get_snapshot(id);
if (snapshot == nullptr)
{
error = "Snapshot does not exist";
return false;
}
switch(orphans)
{
case DENY:
if ( persistent && id == 0 )
{
error = "Cannot delete snapshot 0 for persistent disk images";
return false;
}
ccount = children_count(snapshot, childs);
if ( ccount > 1 )
{
error = "Cannot delete snapshot with more than one children";
return false;
}
else if ( ccount == 0 )
{
return true;
}
else if ( ccount == 1 && active == id)
{
error = "Cannot delete the active snapshot with children";
return false;
}
// -------------------------------------------------------------
// Check the child (ccount == 1)
// -------------------------------------------------------------
if (snapshot->vector_value("CHILDREN", c_id) == 0)
{
c_ccount = children(c_id, childs);
if ( c_ccount > 1 || ( c_ccount == 1 && active == c_id ) )
{
error = "Cannot delete snapshot if child has children";
return false;
}
}
// -----------------------------------------------------------------
// Check the parent
// -----------------------------------------------------------------
if ( snapshot->vector_value("PARENT", p_id) == 0 && p_id != -1)
{
p_ccount = children(p_id, childs);
if ( p_ccount > 1 )
{
error = "Cannot delete snapshot if parent has more than one child";
return false;
}
}
break;
case MIXED:
if (id == active)
{
error = "Cannot delete the active snapshot";
return false;
}
ccount = children_count(snapshot, childs);
if (ccount != 0)
{
error = "Cannot delete snapshot with children";
return false;
}
break;
case ALLOW:
break;
case FORMAT:
//At this point allow orpahn should be mapped to DENY/ALLOW
error = "Inconsistent snapshot orphan mode";
return false;
}
return true;
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
long long Snapshots::get_total_size() const
long long Snapshots::total_size() const
{
long long size_mb, total_mb = 0;

View File

@ -271,8 +271,6 @@ int VirtualMachine::lcm_state_from_str(string& st, LcmState& state)
state = DISK_SNAPSHOT_DELETE_POWEROFF;
} else if ( st == "DISK_SNAPSHOT_SUSPENDED" ) {
state = DISK_SNAPSHOT_SUSPENDED;
} else if ( st == "DISK_SNAPSHOT_REVERT_SUSPENDED" ) {
state = DISK_SNAPSHOT_REVERT_SUSPENDED;
} else if ( st == "DISK_SNAPSHOT_DELETE_SUSPENDED" ) {
state = DISK_SNAPSHOT_DELETE_SUSPENDED;
} else if ( st == "DISK_SNAPSHOT" ) {
@ -420,8 +418,6 @@ string& VirtualMachine::lcm_state_to_str(string& st, LcmState state)
st = "DISK_SNAPSHOT_DELETE_POWEROFF"; break;
case DISK_SNAPSHOT_SUSPENDED:
st = "DISK_SNAPSHOT_SUSPENDED"; break;
case DISK_SNAPSHOT_REVERT_SUSPENDED:
st = "DISK_SNAPSHOT_REVERT_SUSPENDED"; break;
case DISK_SNAPSHOT_DELETE_SUSPENDED:
st = "DISK_SNAPSHOT_DELETE_SUSPENDED"; break;
case DISK_SNAPSHOT:
@ -2734,7 +2730,7 @@ int VirtualMachine::from_xml(const string &xml_str)
break;
}
disks.set_snapshots(snap->get_disk_id(), snap);
disks.set_snapshots(snap->disk_id(), snap);
}
if (!content.empty())
@ -3934,7 +3930,6 @@ void VirtualMachine::get_quota_template(VirtualMachineTemplate& quota_tmpl,
lcm_state != VirtualMachine::DISK_SNAPSHOT_REVERT_POWEROFF &&
lcm_state != VirtualMachine::DISK_SNAPSHOT_DELETE_POWEROFF &&
lcm_state != VirtualMachine::DISK_SNAPSHOT_SUSPENDED &&
lcm_state != VirtualMachine::DISK_SNAPSHOT_REVERT_SUSPENDED &&
lcm_state != VirtualMachine::DISK_SNAPSHOT_DELETE_SUSPENDED &&
lcm_state != VirtualMachine::DISK_RESIZE_POWEROFF &&
lcm_state != VirtualMachine::DISK_RESIZE_UNDEPLOYED &&

View File

@ -284,7 +284,7 @@ int VirtualMachineDisk::create_snapshot(const string& name, string& error)
else
{
snap_id = snapshots->create_snapshot(name, size_mb);
snap_size = snapshots->get_total_size();
snap_size = snapshots->total_size();
}
if (snap_id != -1)
@ -334,21 +334,14 @@ void VirtualMachineDisk::delete_snapshot(int snap_id, Template **ds_quotas,
return;
}
long long ssize = snapshots->get_snapshot_size(snap_id);
long long ssize = snapshots->snapshot_size(snap_id);
snapshots->delete_snapshot(snap_id);
long long snap_size = snapshots->get_total_size();
long long snap_size = snapshots->total_size();
replace("DISK_SNAPSHOT_TOTAL_SIZE", snap_size);
if (snapshots->size() == 0)
{
delete snapshots;
snapshots = 0;
}
string tm_target = get_tm_target();
vm_owner = tm_target == "SELF";

View File

@ -25,41 +25,76 @@ DEP_FILE_LOCATION=$(dirname $DEP_FILE)
mkdir -p $DEP_FILE_LOCATION
cat > $DEP_FILE
#-------------------------------------------------------------------------------
# Resolve disk.<disk_id> links for file-based disks
#-------------------------------------------------------------------------------
FILES=$(xmllint --xpath '//disk[@type="file" and @device="disk"]/source/@file' \
"${DEP_FILE}" | sed -e 's/file=/\n/g' | tr -d '"')
SED_SCRIPT=""
while IFS= read -r f; do
f=$(echo ${f} | sed 's/[[:space:]]*$//')
if ! [[ "${f}" =~ .*/disk\.[0-9]+$ ]]; then
continue
fi
[ ! -L "${f}" ] && continue
rl=$(readlink ${f})
if [ "${rl:0:1}" != "/" ]; then
rl="$(dirname ${f})/${rl}"
fi
[ ! -e "${rl}" ] && continue
SED_SCRIPT="s#source file='${f}'#source file='${rl}'#g;${SED_SCRIPT}"
done <<< ${FILES}
sed -i "${SED_SCRIPT}" "${DEP_FILE}"
#-------------------------------------------------------------------------------
# Compact memory
#-------------------------------------------------------------------------------
if [ "x$CLEANUP_MEMORY_ON_START" = "xyes" ]; then
(sudo -l | grep -q sysctl) && sudo -n sysctl vm.drop_caches=3 vm.compact_memory=1 >/dev/null
fi
#-------------------------------------------------------------------------------
# Create non-volatile memory to store firmware variables if needed
#-------------------------------------------------------------------------------
nvram="$(xmllint --xpath '/domain/os/nvram/text()' $DEP_FILE 2>/dev/null)"
if [ -n "${nvram}" ]; then
cp -n "${OVMF_NVRAM}" "${nvram}"
fi
# Create vGPU following NVIDIA official guide: https://docs.nvidia.com/grid/latest/pdf/grid-vgpu-user-guide.pdf
# Only if supported by node
#-------------------------------------------------------------------------------
# Create vGPU following NVIDIA official guide:
# https://docs.nvidia.com/grid/latest/pdf/grid-vgpu-user-guide.pdf
#-------------------------------------------------------------------------------
(sudo -l | grep -q vgpu) && sudo /var/tmp/one/vgpu "CREATE" "$DEP_FILE_LOCATION/vm.xml"
DATA=`virsh --connect $LIBVIRT_URI create $DEP_FILE`
if [ "x$?" = "x0" ]; then
DOMAIN_ID=$(xmllint --xpath '/domain/name/text()' "$DEP_FILE")
UUID=$(virsh --connect $LIBVIRT_URI dominfo $DOMAIN_ID | awk '/UUID:/ {print $2}')
echo $UUID
# redefine potential snapshots
for SNAPSHOT_MD_XML in $(ls -v ${DEP_FILE_LOCATION}/snap-*.xml 2>/dev/null); do
# replace uuid in the snapshot metadata xml
sed -i "s%<uuid>[[:alnum:]-]*</uuid>%<uuid>$UUID</uuid>%" $SNAPSHOT_MD_XML
# redefine the snapshot using the xml metadata file
virsh --connect $LIBVIRT_URI snapshot-create $DOMAIN_ID $SNAPSHOT_MD_XML --redefine > /dev/null || true
done
else
if [ "x$?" != "x0" ]; then
error_message "Could not create domain from $DEP_FILE"
exit -1
fi
DOMAIN_ID=$(xmllint --xpath '/domain/name/text()' "$DEP_FILE")
UUID=$(virsh --connect $LIBVIRT_URI dominfo $DOMAIN_ID | awk '/UUID:/ {print $2}')
echo $UUID
#---------------------------------------------------------------------------
# redefine potential snapshots
#---------------------------------------------------------------------------
for SNAPSHOT_MD_XML in $(ls -v ${DEP_FILE_LOCATION}/snap-*.xml 2>/dev/null); do
# replace uuid in the snapshot metadata xml
sed -i "s%<uuid>[[:alnum:]-]*</uuid>%<uuid>$UUID</uuid>%" $SNAPSHOT_MD_XML
# redefine the snapshot using the xml metadata file
virsh --connect $LIBVIRT_URI snapshot-create $DOMAIN_ID $SNAPSHOT_MD_XML --redefine > /dev/null || true
done

View File

@ -258,6 +258,10 @@ if [ "$SHARED" = "YES" ] && [ -n "$SNAP_CUR" ]; then
DISK_PATH=$(virsh --connect $QEMU_PROTOCOL://$DEST_HOST/system domblklist $DEPLOY_ID | awk '/disk.0/ {print $2}')
DISK_DIR=$(dirname $DISK_PATH)
if [[ "${DISK_DIR}" =~ .*/disk\.[0-9]+.snap$ ]]; then
DISK_DIR=$(dirname $DISK_DIR)
fi
for SNAPSHOT_MD_XML in $(ls -v ${DISK_DIR}/snap-*.xml 2>/dev/null); do
# replace uuid in the snapshot metadata xml
sed -i "s%<uuid>[[:alnum:]-]*</uuid>%<uuid>$UUID</uuid>%" $SNAPSHOT_MD_XML