Hello Dan Kenigsberg, Deepak C Shetty,
I'd like you to do a code review. Please visit
http://gerrit.ovirt.org/20189
to review the following change.
Change subject: vm: add the transient disk support
......................................................................
vm: add the transient disk support
A transient disk is a temporary qcow layer based on a (usually
internal) volume that is attached to a VM in read-write mode.
All changes stored in the transient disk will be lost as soon
as the disk is hot-unplugged or when the VM exits.
Its primary use is to access in read-write mode a snapshot
(read-only volume).
Libvirt doesn't support transient disks yet, hence this patch
includes also all the required parts to fill that gap.
Bug-Url:
https://bugzilla.redhat.com/show_bug.cgi?id=1019205
Change-Id: I3dfec35e324c47d8c86a965947e3ae4ae48c7524
Signed-off-by: Deepak C Shetty <deepakcs(a)linux.vnet.ibm.com>
Signed-off-by: Federico Simoncelli <fsimonce(a)redhat.com>
Reviewed-on:
http://gerrit.ovirt.org/18326
Reviewed-by: Dan Kenigsberg <danken(a)redhat.com>
Signed-off-by: Federico Simoncelli <fsimonce(a)redhat.com>
---
M init/vdsmd_init_common.sh.in
M lib/vdsm/config.py.in
M lib/vdsm/define.py
M lib/vdsm/tool/Makefile.am
A lib/vdsm/tool/transient.py
M tests/vmTests.py
M vdsm.spec.in
M vdsm/vm.py
8 files changed, 196 insertions(+), 20 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/89/20189/1
diff --git a/init/vdsmd_init_common.sh.in b/init/vdsmd_init_common.sh.in
index 38da542..4fef62c 100644
--- a/init/vdsmd_init_common.sh.in
+++ b/init/vdsmd_init_common.sh.in
@@ -59,6 +59,12 @@
}
+task_prepare_transient_repository() {
+ "$VDSM_TOOL" setup-transient-repository
+ "$VDSM_TOOL" cleanup-transient-repository
+}
+
+
task_syslog_available() {
if ! [ -S "/dev/log" ]; then
echo "Cannot communicate with syslog deamon for reports. " \
@@ -203,8 +209,9 @@
--pre-start)
run_tasks " \
run_init_hooks gencerts reconfigure_sanlock reconfigure_libvirt \
- syslog_available nwfilter dummybr load_needed_modules \
- tune_system mkdirs test_space test_lo test_conflicting_conf"
+ prepare_transient_repository syslog_available nwfilter dummybr \
+ load_needed_modules tune_system mkdirs test_space test_lo \
+ test_conflicting_conf"
;;
--post-stop)
run_tasks "run_final_hooks"
diff --git a/lib/vdsm/config.py.in b/lib/vdsm/config.py.in
index a229998..f244510 100644
--- a/lib/vdsm/config.py.in
+++ b/lib/vdsm/config.py.in
@@ -154,6 +154,9 @@
('libvirt_env_variable_log_outputs', '',
'Specify the output to track libvirt calls'),
+
+ ('transient_disks_repository', '@VDSMLIBDIR@/transient',
+ 'Local path to the transient disks repository.'),
]),
# Section: [ksm]
diff --git a/lib/vdsm/define.py b/lib/vdsm/define.py
index e38fce1..eb78633 100644
--- a/lib/vdsm/define.py
+++ b/lib/vdsm/define.py
@@ -129,6 +129,9 @@
'resizeErr': {'status': {
'code': 58,
'message': 'Wrong resize disk parameter'}},
+ 'transientErr': {'status': {
+ 'code': 59,
+ 'message': 'Action not permitted on a VM with transient
disks'}},
'recovery': {'status': {
'code': 99,
'message': 'Recovering from crash or Initializing'}},
diff --git a/lib/vdsm/tool/Makefile.am b/lib/vdsm/tool/Makefile.am
index 9f00984..7a4e095 100644
--- a/lib/vdsm/tool/Makefile.am
+++ b/lib/vdsm/tool/Makefile.am
@@ -43,6 +43,7 @@
sanlock.py \
seboolsetup.py \
service.py \
+ transient.py \
vdsm-id.py \
$(NULL)
diff --git a/lib/vdsm/tool/transient.py b/lib/vdsm/tool/transient.py
new file mode 100644
index 0000000..7452f30
--- /dev/null
+++ b/lib/vdsm/tool/transient.py
@@ -0,0 +1,86 @@
+# Copyright 2013 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import os
+import glob
+import pwd
+import selinux
+
+from vdsm import constants
+from vdsm.config import config
+from vdsm.tool import expose
+from vdsm.utils import execCmd, CommandPath
+
+
+SELINUX_VIRT_IMAGE_LABEL = "system_u:object_r:virt_image_t:s0"
+TRANSIENT_DISKS_REPO = config.get('vars', 'transient_disks_repository')
+
+_fuser = CommandPath(
+ "fuser",
+ "/sbin/fuser", # Fedora, EL6
+)
+
+
+@expose("setup-transient-repository")
+def setup_transient_repository(*args):
+ """
+ Prepare the transient disks repository
+ """
+ _, _, vdsm_uid, vdsm_gid, _, _, _ = pwd.getpwnam(constants.VDSM_USER)
+
+ try:
+ os.makedirs(TRANSIENT_DISKS_REPO)
+ except OSError as e:
+ if e.errno != os.errno.EEXIST:
+ raise
+
+ os.chown(TRANSIENT_DISKS_REPO, vdsm_uid, vdsm_gid)
+ os.chmod(TRANSIENT_DISKS_REPO, 0750)
+ selinux.chcon(TRANSIENT_DISKS_REPO, SELINUX_VIRT_IMAGE_LABEL)
+
+
+@expose("cleanup-transient-repository")
+def cleanup_transient_repository(*args):
+ """
+ Cleanup the unused transient disks present in the repository.
+ (NOTE: it is recommended to NOT execute this command when the vdsm
+ daemon is running)
+ """
+ transient_images = set(glob.glob(os.path.join(TRANSIENT_DISKS_REPO, "*")))
+
+ if len(transient_images) == 0:
+ return # Nothing to do
+
+ cmd_ret, cmd_out, cmd_err = execCmd([_fuser.cmd] + list(transient_images))
+ # According to: "fuser returns a non-zero return code if none of the
+ # specified files is accessed or in case of a fatal error. If at least
+ # one access has been found, fuser returns zero." we can discard the
+ # return code.
+ # NOTE: the list of open files is printed to cmd_err with an extra ":"
+ # character appended (removed by [:-1]).
+ open_transient_images = set(x[:-1] for x in cmd_err)
+
+ for image_path in transient_images - open_transient_images:
+ # NOTE: This could cause a race with the creation of a virtual
+ # machine with a transient disk (if vdsm is running).
+ try:
+ os.unlink(image_path)
+ except OSError as e:
+ if e.errno != os.errno.ENOENT:
+ raise
diff --git a/tests/vmTests.py b/tests/vmTests.py
index c03cd5f..1f69f0a 100644
--- a/tests/vmTests.py
+++ b/tests/vmTests.py
@@ -367,6 +367,7 @@
{},
# New extended values
{'shared': 'exclusive'}, {'shared':
'shared'}, {'shared': 'none'},
+ {'shared': 'transient'},
]
expectedStates = [
@@ -375,7 +376,7 @@
# Missing shared definition
'none',
# New extended values
- 'exclusive', 'shared', 'none',
+ 'exclusive', 'shared', 'none', 'transient',
]
driveConfig = {'index': '0', 'iface': 'virtio',
'device': 'disk'}
diff --git a/vdsm.spec.in b/vdsm.spec.in
index e175ff8..a9e8f7d 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -1053,6 +1053,7 @@
%{python_sitearch}/%{vdsm_name}/tool/sanlock.py*
%{python_sitearch}/%{vdsm_name}/tool/seboolsetup.py*
%{python_sitearch}/%{vdsm_name}/tool/service.py*
+%{python_sitearch}/%{vdsm_name}/tool/transient.py*
%{python_sitearch}/%{vdsm_name}/tool/validate_ovirt_certs.py*
%{python_sitearch}/%{vdsm_name}/tool/vdsm-id.py*
diff --git a/vdsm/vm.py b/vdsm/vm.py
index 1a16d53..244e7b0 100644
--- a/vdsm/vm.py
+++ b/vdsm/vm.py
@@ -428,11 +428,12 @@
NONE = "none"
EXCLUSIVE = "exclusive"
SHARED = "shared"
+ TRANSIENT = "transient"
@classmethod
def getAllValues(cls):
# TODO: use introspection
- return (cls.NONE, cls.EXCLUSIVE, cls.SHARED)
+ return (cls.NONE, cls.EXCLUSIVE, cls.SHARED, cls.TRANSIENT)
# These strings are representing libvirt virDomainEventType values
@@ -1341,6 +1342,7 @@
self.truesize = int(kwargs.get('truesize', '0'))
self.apparentsize = int(kwargs.get('apparentsize', '0'))
self.name = self._makeName()
+ self.cache = config.get('vars', 'qemu_drive_cache')
if self.device in ("cdrom", "floppy"):
self._blockDev = False
@@ -1441,15 +1443,25 @@
"block device", self.path, exc_info=True)
return self._blockDev
+ @property
+ def transientDisk(self):
+ # Using getattr to handle legacy and removable drives.
+ return getattr(self, 'shared', None) == DRIVE_SHARED_TYPE.TRANSIENT
+
def _customize(self):
- # Customize disk device
- if self.iface == 'virtio':
+ if self.transientDisk:
+ # Force the cache to be writethrough, which is qemu's default.
+ # This is done to ensure that we don't ever use cache=none for
+ # transient disks, since we create them in /var/run/vdsm which
+ # may end up on tmpfs and don't support O_DIRECT, and qemu uses
+ # O_DIRECT when cache=none and hence hotplug might fail with
+ # error that one can take eternity to debug the reason behind it!
+ self.cache = "writethrough"
+ elif self.iface == 'virtio':
try:
self.cache = self.conf['custom']['viodiskcache']
except KeyError:
- self.cache = config.get('vars', 'qemu_drive_cache')
- else:
- self.cache = config.get('vars', 'qemu_drive_cache')
+ pass # Ignore if custom disk cache is missing
def _makeName(self):
devname = {'ide': 'hd', 'scsi': 'sd',
'virtio': 'vd', 'fdc': 'fd'}
@@ -2159,6 +2171,10 @@
# Now we got all the resources we needed
self.startDisksStatsCollection()
+ def _prepareTransientDisks(self, drives):
+ for drive in drives:
+ self._createTransientDisk(drive)
+
def _onQemuDeath(self):
self.log.info('underlying process disconnected')
# Try release VM resources first, if failed stuck in 'Powering Down'
@@ -2482,6 +2498,13 @@
with self._volPrepareLock:
for drive in drives:
try:
+ self._removeTransientDisk(drive)
+ except:
+ self.log.warning("Drive transient volume deletion failed "
+ "for drive %s", drive, exc_info=True)
+ # Skip any exception as we don't want to interrupt the
+ # teardown process for any reason.
+ try:
self.cif.teardownVolumePath(drive)
except:
self.log.error("Drive teardown failure for %s",
@@ -2644,12 +2667,20 @@
def isMigrating(self):
return self._migrationSourceThread.isAlive()
+ def hasTransientDisks(self):
+ for drive in self._devices[DISK_DEVICES]:
+ if drive.transientDisk:
+ return True
+ return False
+
def migrate(self, params):
self._acquireCpuLockWithTimeout()
try:
if self.isMigrating():
self.log.warning('vm already migrating')
return errCode['exist']
+ if self.hasTransientDisks():
+ return errCode['transientErr']
# while we were blocking, another migrationSourceThread could have
# taken self Down
if self._lastStatus == 'Down':
@@ -2871,6 +2902,7 @@
if not 'recover' in self.conf:
devices = self.buildConfDevices()
self.preparePaths(devices[DISK_DEVICES])
+ self._prepareTransientDisks(devices[DISK_DEVICES])
# Update self.conf with updated devices
# For old type vmParams, new 'devices' key will be
# created with all devices info
@@ -3266,6 +3298,40 @@
params=customProps)
return {'status': doneCode, 'vmList': self.status()}
+ def _createTransientDisk(self, diskParams):
+ if diskParams.get('shared', None) != DRIVE_SHARED_TYPE.TRANSIENT:
+ return
+
+ # FIXME: This should be replaced in future the support for transient
+ # disk in libvirt (BZ#832194)
+ driveFormat = (
+ qemuImg.FORMAT.QCOW2 if diskParams['format'] == 'cow' else
+ qemuImg.FORMAT.RAW
+ )
+
+ transientHandle, transientPath = tempfile.mkstemp(
+ dir=config.get('vars', 'transient_disks_repository'),
+ prefix="%s-%s." % (diskParams['domainID'],
diskParams['volumeID']))
+
+ try:
+ qemuImg.create(transientPath, format=qemuImg.FORMAT.QCOW2,
+ backing=diskParams['path'],
+ backingFormat=driveFormat)
+ os.fchmod(transientHandle, 0660)
+ except:
+ os.unlink(transientPath) # Closing after deletion is correct
+ self.log.error("Failed to create the transient disk for "
+ "volume %s", diskParams['volumeID'],
exc_info=True)
+ finally:
+ os.close(transientHandle)
+
+ diskParams['path'] = transientPath
+ diskParams['format'] = 'cow'
+
+ def _removeTransientDisk(self, drive):
+ if drive.transientDisk:
+ os.unlink(drive.path)
+
def hotplugDisk(self, params):
if self.isMigrating():
return errCode['migInProgress']
@@ -3276,6 +3342,7 @@
if vdsmImg:
self._normalizeVdsmImg(diskParams)
+ self._createTransientDisk(diskParams)
self.updateDriveIndex(diskParams)
drive = Drive(self.conf, self.log, **diskParams)
@@ -3324,17 +3391,15 @@
diskParams = params.get('drive', {})
diskParams['path'] = self.cif.prepareVolumePath(diskParams)
- # Find disk object in vm's drives list
- for drv in self._devices[DISK_DEVICES][:]:
- if drv.path == diskParams['path']:
- drive = drv
- break
- else:
+ try:
+ drive = self._findDriveByUUIDs(diskParams)
+ except LookupError:
self.log.error("Hotunplug disk failed - Disk not found: %s",
diskParams)
- return {'status': {'code': errCode['hotunplugDisk']
- ['status']['code'],
- 'message': "Disk not found"}}
+ return {'status': {
+ 'code':
errCode['hotunplugDisk']['status']['code'],
+ 'message': "Disk not found"
+ }}
if drive.hasVolumeLeases:
return errCode['noimpl']
@@ -3350,7 +3415,7 @@
diskDev = None
for dev in self.conf['devices'][:]:
if (dev['type'] == DISK_DEVICES and
- dev['path'] == diskParams['path']):
+ dev['path'] == drive.path):
with self._confLock:
self.conf['devices'].remove(dev)
diskDev = dev
@@ -3600,6 +3665,9 @@
if self.isMigrating():
return errCode['migInProgress']
+ if self.hasTransientDisks():
+ return errCode['transientErr']
+
for drive in snapDrives:
baseDrv, tgetDrv = _normSnapDriveParams(drive)
@@ -3794,7 +3862,7 @@
if not mergeDrive or not hasattr(mergeDrive, 'volumeChain'):
mergeStatus['status'] = MERGESTATUS.DRIVE_NOT_FOUND
- elif mergeDrive.hasVolumeLeases:
+ elif mergeDrive.hasVolumeLeases or mergeDrive.transientDisk:
mergeStatus['status'] = MERGESTATUS.DRIVE_NOT_SUPPORTED
else:
for volume in mergeDrive.volumeChain:
@@ -3894,6 +3962,9 @@
if srcDrive.hasVolumeLeases:
return errCode['noimpl']
+ if srcDrive.transientDisk:
+ return errCode['transientErr']
+
try:
self._setDiskReplica(srcDrive, dstDisk)
except:
@@ -3944,6 +4015,9 @@
if srcDrive.hasVolumeLeases:
return errCode['noimpl']
+ if srcDrive.transientDisk:
+ return errCode['transientErr']
+
if not srcDrive.isDiskReplicationInProgress():
return errCode['replicaErr']
--
To view, visit
http://gerrit.ovirt.org/20189
To unsubscribe, visit
http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I3dfec35e324c47d8c86a965947e3ae4ae48c7524
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: ovirt-3.3
Gerrit-Owner: Federico Simoncelli <fsimonce(a)redhat.com>
Gerrit-Reviewer: Dan Kenigsberg <danken(a)redhat.com>
Gerrit-Reviewer: Deepak C Shetty <deepakcs(a)linux.vnet.ibm.com>