Permalink
Browse files

Add support for booting KVM machines from qcow2 images

Detect qcow2 images by reading the image file header.  It contains the
"QFI" string and the qcow version number.  The libvirt template is
configured to use the qcow2 driver when a qcow2 image is detected.

The root partition is altered by attaching it to a host device using
qemu-nbd.  Since qemu-nbd can be named differently depending on the
Linux distribution, it is configurable by mount.conf.

Closes #105.
  • Loading branch information...
priteau committed Jul 10, 2012
1 parent a7d7af3 commit eeccbccd754e1937f219b47821bf2f0b96ca7bc4
@@ -31,7 +31,13 @@
<devices>
{% for disk in devices.disks %}
<disk type='{{ disk._type }}'>
- {% if disk.driver %}{% if disk.driver == "tap:aio" %}<driver name='tap' type='aio' />{% endif %}{% endif %}
+ {% if disk.driver %}
+ {% if disk.driver == "tap:aio" %}
+ <driver name='tap' type='aio' />
+ {% elif disk.driver == "qemu:qcow2" %}
+ <driver name='qemu' type='qcow2'/>
+ {% endif %}
+ {% endif %}
{% if disk._type == "block" %}
<source dev='{{ disk.source }}' />
@@ -44,3 +44,10 @@ tmpdir: tmp
fdisk: /sbin/fdisk
+# qemu-nbd is used when an HD image is in qcow2 format. qemu-nbd will be used
+# to attach the HD image as an nbd device, which allows to mount the root
+# partition inside.
+#
+# If this setting is empty or missing, qcow2 support will be disabled.
+
+qemu_nbd: /usr/bin/kvm-nbd
@@ -84,6 +84,8 @@ UMOUNT="/bin/umount"
CP="/bin/cp"
MKDIR="/bin/mkdir"
CHMOD="/bin/chmod"
+MODPROBE="/sbin/modprobe"
+EXPR="/usr/bin/expr"
FLOCKFILE=/opt/nimbus/var/workspace-control/lock/loopback.lock
FLOCK=/usr/bin/flock
@@ -156,6 +158,8 @@ elif [ "$subcommand" = "one" ]; then
subcommand="ONE"
elif [ "$subcommand" = "hdone" ]; then
subcommand="HDONE"
+elif [ "$subcommand" = "qcowone" ]; then
+ subcommand="QCOWONE"
else
echo "invalid subcommand"
exit 1
@@ -196,6 +200,18 @@ elif [ "$subcommand" = "HDONE" ]; then
sourcefiles="$datafile"
targetfiles="$datatarget"
+elif [ "$subcommand" = "QCOWONE" ]; then
+ if [ $# -ne 6 ]; then
+ echo "qcowone subcommand requires 6 and only 6 arguments: qcowone <imagefile> <mntpoint> <datafile> <datatarget> <qemu-nbd-path>"
+ exit 1
+ fi
+ echo " - datafile: $datafile"
+ echo " - datatarget: $datatarget"
+
+ sourcefiles="$datafile"
+ targetfiles="$datatarget"
+ qemu_nbd=$6
+
else
echo "??"
exit 64
@@ -315,17 +331,78 @@ done
# ALTER IMAGE #
###############
+function qemu_nbd_disconnect () {
+ cmd="$qemu_nbd -d /dev/nbd0"
+
+ echo "command = $cmd"
+ if [ "$DRYRUN" != "true" ]; then
+ ( $cmd )
+ if [ $? -ne 0 ]; then
+ # nbd will print to stderr
+ problem="true"
+ else
+ echo " - successful"
+ fi
+ fi
+}
+
(
$FLOCK -x 200
echo ""
echo "Altering image (dryrun = $DRYRUN):"
echo ""
+problem="false"
+
+if [ "$subcommand" = "QCOWONE" ]; then
+ cmd="$MODPROBE nbd max_part=8"
+ echo "command = $cmd"
+ if [ "$DRYRUN" != "true" ]; then
+ ( $cmd )
+ if [ $? -ne 0 ]; then
+ # xm will print to stderr
+ exit 5
+ else
+ echo " - successful"
+ fi
+ fi
+
+ cmd="$qemu_nbd --connect /dev/nbd0 $imagefile"
+
+ echo "command = $cmd"
+ if [ "$DRYRUN" != "true" ]; then
+ ( $cmd )
+ if [ $? -ne 0 ]; then
+ # xm will print to stderr
+ exit 6
+ else
+ echo " - successful"
+ fi
+ fi
+
+ # qemu-nbd takes some time to connect the image to the device.
+ # Wait for maximum 30 seconds for nbd0p1 to show up.
+ i=30
+ while [ $i -ne 0 ]; do
+ ls /dev/nbd0p1 > /dev/null 2>&1 && break
+ i=`$EXPR $i - 1`
+ echo "No /dev/nbd0p1 yet, will try again for $i seconds"
+ sleep 1
+ done
+ if ! ls /dev/nbd0p1 > /dev/null 2>&1; then
+ echo "Waited 30 seconds but no /dev/nbd0p1 showed up, exiting"
+ qemu_nbd_disconnect
+ exit 66
+ fi
+fi
+
if [ "$subcommand" = "ONE" ]; then
cmd="$MOUNT -o loop,noexec,nosuid,nodev,noatime,sync $imagefile $mountpoint"
elif [ "$subcommand" = "HDONE" ]; then
cmd="$MOUNT -o loop,noexec,nosuid,nodev,noatime,sync,offset=$offsetint $imagefile $mountpoint"
+elif [ "$subcommand" = "QCOWONE" ]; then
+ cmd="$MOUNT /dev/nbd0p1 $mountpoint"
else
echo "??"
exit 65
@@ -336,14 +413,16 @@ if [ "$DRYRUN" != "true" ]; then
( $cmd )
if [ $? -ne 0 ]; then
# mount will print to stderr
+ if [ "$subcommand" = "QCOWONE" ]; then
+ # disconnect the nbd device
+ qemu_nbd_disconnect
+ fi
exit 5
else
echo " - successful"
fi
fi
-problem="false"
-
if [ "$CREATE_SSH_DIR" == "true" -a "$datatarget" == "/root/.ssh/authorized_keys" -a -d "$mountpoint/root" ]; then
cmd="$MKDIR -p $mountpoint/root/.ssh"
echo "command = $cmd"
@@ -372,6 +451,8 @@ if [ "$subcommand" = "ONE" ]; then
cmd="$CP $datafile $mountpoint/$datatarget"
elif [ "$subcommand" = "HDONE" ]; then
cmd="$CP $datafile $mountpoint/$datatarget"
+elif [ "$subcommand" = "QCOWONE" ]; then
+ cmd="$CP $datafile $mountpoint/$datatarget"
else
echo "??"
problem="true"
@@ -394,14 +475,19 @@ if [ "$DRYRUN" != "true" ]; then
( $cmd )
if [ $? -ne 0 ]; then
# umount will print to stderr
- exit 6
+ problem="true"
else
echo " - successful"
- # notify that one or more CP invocations did not succeed:
- if [ "$problem" = "true" ]; then
- exit 7
- fi
fi
fi
+if [ "$subcommand" = "QCOWONE" ]; then
+ qemu_nbd_disconnect
+fi
+
+# notify that one or more command invocations did not succeed:
+if [ "$problem" = "true" ]; then
+ exit 7
+fi
+
) 200<$FLOCKFILE
@@ -3,6 +3,7 @@
import re
import shutil
import stat
+import struct
import sys
import zope.interface
@@ -35,6 +36,7 @@ def __init__(self, params, common):
self.sudo_path = None
self.mounttool_path = None
self.fdisk_path = None
+ self.qemu_nbd_path = None
self.mountdir = None
self.tmpdir = None
@@ -46,6 +48,10 @@ def validate(self):
self.fdisk_path = self.p.get_conf_or_none("mount", "fdisk")
if not self.fdisk_path:
self.c.log.warn("no fdisk configuration, mount+edit functionality for HD images is disabled")
+
+ self.qemu_nbd_path = self.p.get_conf_or_none("mount", "qemu_nbd")
+ if not self.qemu_nbd_path:
+ self.c.log.warn("no qemu_nbd configuration, mount+edit functionality for qcow2 images is disabled")
# if functionality is disabled but arg exists, should fail program
self._validate_args_if_exist()
@@ -447,9 +453,29 @@ def _doOneMountCopyTask(self, imagepath, src, dst, mntpath, hdimage):
error = None
try:
- offsetint = self._guess_offset(imagepath)
- cmd = "%s %s hdone %s %s %s %s %d" % (self.sudo_path, self.mounttool_path, imagepath, mntpath, src, dst, offsetint)
- error = self._doOneMountCopyInnerTask(src, cmd)
+ f = open(imagepath, 'r')
+ magic = f.read(4)
+
+ # Version number (1 or 2) is in big endian format.
+ # We only support version 2 (qcow2).
+ be_version = f.read(4)
+ version = struct.unpack('>I', be_version)[0]
+ f.close()
+
+ if magic[0:3] == 'QFI':
+ if version == 2:
+ if self.qemu_nbd_path:
+ # Mounting the partition as a qcow2 image
+ cmd = "%s %s qcowone %s %s %s %s %s" % (self.sudo_path, self.mounttool_path, imagepath, mntpath, src, dst, self.qemu_nbd_path)
+ error = self._doOneMountCopyInnerTask(src, cmd)
+ else:
+ raise IncompatibleEnvironment("qcow2 image detected, but qemu_nbd configuration is missing from mount.conf")
+ else:
+ raise IncompatibleEnvironment("qcow image detected with unsupported version number %d" % version)
+ else:
+ offsetint = self._guess_offset(imagepath)
+ cmd = "%s %s hdone %s %s %s %s %d" % (self.sudo_path, self.mounttool_path, imagepath, mntpath, src, dst, offsetint)
+ error = self._doOneMountCopyInnerTask(src, cmd)
except Exception,e:
error = e
@@ -1,10 +1,13 @@
import os
+import struct
+
from lvrt_adapter import PlatformAdapter, PlatformInputAdapter
from workspacecontrol.api.exceptions import *
import lvrt_model
+
class vmmadapter(PlatformAdapter):
-
+
def __init__(self, params, common):
PlatformAdapter.__init__(self, params, common)
other_uri = self.p.get_conf_or_none("libvirt_connections", "kvm0")
@@ -13,15 +16,30 @@ def __init__(self, params, common):
else:
self.connection_uri = "qemu:///system"
self.c.log.debug("KVM libvirt URI: '%s'" % self.connection_uri)
-
+
def validate(self):
self.c.log.debug("validating libvirt kvm adapter")
+
class intakeadapter(PlatformInputAdapter):
def __init__(self, params, common):
PlatformInputAdapter.__init__(self, params, common)
-
+
def fill_model(self, dom, local_file_set, nic_set, kernel):
dom._type = "kvm"
dom.os.type = "hvm"
-
+
+ for disk in dom.devices.disks:
+ imagepath = disk.source
+ f = open(imagepath, 'r')
+ magic = f.read(4)
+
+ # Version number (1 or 2) is in big endian format.
+ # We only support version 2 (qcow2).
+ be_version = f.read(4)
+ version = struct.unpack('>I', be_version)[0]
+ f.close()
+
+ if magic[0:3] == 'QFI' and version == 2:
+ # The partition is a qcow2 image
+ disk.driver = "qemu:qcow2"

0 comments on commit eeccbcc

Please sign in to comment.