Currently tests depends on the kernel nbd module to the test image. But
the nbd module may not exist (e.g. Gitlab CI's shared runner doesn't
provide it) or we simply don't have the root permission to use /dev/nbd
(e.g. in a rootless container) for security concerns. To address these
limitations, use libguestfs's guestmount/guestunmount to mount/unmount
when USE_GUESTMOUNT=1. Another benefit of guestmount is it can handle
the case where a qemu image has multiple partitions automatically.
Note
1. guestmount doesn't need root permission but dnf currently mandates
sudo. So still use sudo to call guestmount to simply make dnf happy.
pkcon doesn't require sudo but unfornately it doesn't support
--installroot.
2. guestunmount is similar to an async command, so we need to implement
our own logic to wait for unmounting to be truly finished.
Signed-off-by: Coiby Xu <coxu(a)redhat.com>
---
tests/scripts/build-image.sh | 6 +++
tests/scripts/image-init-lib.sh | 94 +++++++++++++++++++++++++--------
2 files changed, 77 insertions(+), 23 deletions(-)
diff --git a/tests/scripts/build-image.sh b/tests/scripts/build-image.sh
index c196bfb..f8d8485 100755
--- a/tests/scripts/build-image.sh
+++ b/tests/scripts/build-image.sh
@@ -54,4 +54,10 @@ img_add_qemu_cmd() {
[ -e "$INST_SCRIPT" ] && source $INST_SCRIPT
+if [[ $USE_GUESTMOUNT ]]; then
+ # unmount the image before renaming it otherwise sync_guestunmount may falsely
+ # think that the image has been truely unmounted
+ sync_guestunmount $OUTPUT_IMAGE.building ${MNTS[$OUTPUT_IMAGE.building]}
+fi
+
mv $OUTPUT_IMAGE.building $OUTPUT_IMAGE
diff --git a/tests/scripts/image-init-lib.sh b/tests/scripts/image-init-lib.sh
index 467f32f..39b20f0 100644
--- a/tests/scripts/image-init-lib.sh
+++ b/tests/scripts/image-init-lib.sh
@@ -20,20 +20,64 @@ is_mounted()
findmnt -k -n $1 &>/dev/null
}
-clean_up()
+is_qemu_image_locked()
{
- for _mnt in ${MNTS[@]}; do
- is_mounted $_mnt && $SUDO umount -f -R $_mnt
+ qemu-img info $1 2>&1 >/dev/null | grep -q "lock"
+}
+
+# call guestmount and wait for image is truely umounted.
+#
+# guestumount simply asks qemu to quit. When guesumount returns, qemu may still
+# in the quiting process. In this case, if we start a new qemu instance, it will
+# complain with 'Failed to get shared "write" lock'.
+sync_guestunmount() {
+ local i _image _mnt _wait_times
+
+ DEFAULT_TIMES=100
+ _image=$1
+ _mnt=$2
+ i=0
+
+ $SUDO LIBGUESTFS_BACKEND=direct guestunmount $_mnt
+
+ if [[ $GUEST_UNMOUNT_WAIT_TIMES ]]; then
+ _wait_times=$GUEST_UNMOUNT_WAIT_TIMES
+ else
+ _wait_times=$DEFAULT_TIMES
+ fi
+
+ # wait 10s at maximum for $_image to be available
+ while is_qemu_image_locked $_image && [[ $i -lt $_wait_times ]]; do
+ i=$[$i+1]
+ sleep 0.1
done
- for _dev in ${DEVS[@]}; do
- [ ! -e "$_dev" ] && continue
- [[ "$_dev" == "/dev/loop"* ]] && $SUDO losetup -d
"$_dev"
- [[ "$_dev" == "/dev/nbd"* ]] && $SUDO qemu-nbd --disconnect
"$_dev"
+ if [[ $i == $_wait_times ]]; then
+ perror_exit "After 0.1*$_wait_times seconds, $_image is still locked. You may need
to increase GUEST_UNMOUNT_WAIT_TIMES (default=$DEFAULT_TIMES)"
+ fi
+}
+
+clean_up()
+{
+ for _image in ${!MNTS[@]}; do
+ _mnt=${MNTS[$_image]}
+ if [[ $USE_GUESTMOUNT ]] && is_mounted $_mnt; then
+ sync_guestunmount $_image $_mnt
+ else
+ is_mounted $_mnt && $SUDO umount -f -R $_mnt
+ fi
+ rm $_image.lock
done
- [ -d "$TMPDIR" ] && $SUDO rm --one-file-system -rf --
"$TMPDIR";
+ if [[ ! $USE_GUESTMOUNT ]]; then
+ for _dev in ${DEVS[@]}; do
+ [ ! -e "$_dev" ] && continue
+ [[ "$_dev" == "/dev/loop"* ]] && $SUDO losetup -d
"$_dev"
+ [[ "$_dev" == "/dev/nbd"* ]] && $SUDO qemu-nbd
--disconnect "$_dev"
+ done
+ fi
+ [ -d "$TMPDIR" ] && $SUDO rm --one-file-system -rf --
"$TMPDIR";
sync
}
@@ -159,10 +203,11 @@ mount_image() {
[ $? -ne 0 ] || [ -z "$dev" ] && perror_exit "failed to setup
loop device"
elif fmt_is_qcow2 "$fmt"; then
- prepare_nbd
-
- dev=$(mount_nbd $image)
- [ $? -ne 0 ] || [ -z "$dev" ] perror_exit "failed to connect qemu to nbd
device '$dev'"
+ if [[ ! $USE_GUESTMOUNT ]]; then
+ prepare_nbd
+ dev=$(mount_nbd $image)
+ [ $? -ne 0 ] || [ -z "$dev" ] perror_exit "failed to connect qemu to
nbd device '$dev'"
+ fi
else
perror_exit "Unrecognized image format '$fmt'"
fi
@@ -172,16 +217,19 @@ mount_image() {
[ $? -ne 0 ] || [ -z "$mnt" ] && perror_exit "failed to create
tmp mount dir"
MNTS[$image]="$mnt"
- mnt_dev=$(get_mountable_dev "$dev")
- [ $? -ne 0 ] || [ -z "$mnt_dev" ] && perror_exit "failed to setup
loop device"
-
- $SUDO mount $mnt_dev $mnt
- [ $? -ne 0 ] && perror_exit "failed to mount device
'$mnt_dev'"
- boot=$(get_mount_boot "$dev")
- if [[ -n "$boot" ]]; then
- root=$(get_image_mount_root $image)
- $SUDO mount $boot $root/boot
- [ $? -ne 0 ] && perror_exit "failed to mount the bootable partition for
device '$mnt_dev'"
+ if [[ $USE_GUESTMOUNT ]]; then
+ $SUDO LIBGUESTFS_BACKEND=direct guestmount -a $image -i $mnt
+ else
+ mnt_dev=$(get_mountable_dev "$dev")
+ [ $? -ne 0 ] || [ -z "$mnt_dev" ] && perror_exit "failed to
setup loop device"
+ $SUDO mount $mnt_dev $mnt
+ [ $? -ne 0 ] && perror_exit "failed to mount device
'$mnt_dev'"
+ boot=$(get_mount_boot "$dev")
+ if [[ -n "$boot" ]]; then
+ root=$(get_image_mount_root $image)
+ $SUDO mount $boot $root/boot
+ [ $? -ne 0 ] && perror_exit "failed to mount the bootable partition for
device '$mnt_dev'"
+ fi
fi
}
@@ -218,7 +266,7 @@ inst_pkg_in_image() {
# if [ "$distro" != "Fedora" ]; then
# perror_exit "only Fedora image is supported"
# fi
- release=$(cat $root/etc/fedora-release | sed -n
"s/.*[Rr]elease\s*\([0-9]*\).*/\1/p")
+ release=$(sudo cat $root/etc/fedora-release | sed -n
"s/.*[Rr]elease\s*\([0-9]*\).*/\1/p")
[ $? -ne 0 ] || [ -z "$release" ] && perror_exit "only Fedora
image is supported"
$SUDO dnf --releasever=$release --installroot=$root install -y $@
--
2.37.1