Nir Soffer has uploaded a new change for review.
Change subject: libvirtconnection: Replace assert with AssertionError
......................................................................
libvirtconnection: Replace assert with AssertionError
The code wrongly assumed that assert always exists. When running in
optimized mode, the check would be skipped, and instead of getting an
AssertionError, which is the expected error for programmer error
(starting the eventloop twice), we could get a confusing
RuntimeException or RuntimeError from Thread.start (depending on Python
version).
RuntimeError misused in the standard library for all kinds of errors
that do not have builtin errors. It is particularry bad option when used
for usage error.
Change-Id: Icf1564f81f4c1fbf77ccaff6d93c047a02d946da
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/libvirtconnection.py
1 file changed, 2 insertions(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/64/34364/1
diff --git a/lib/vdsm/libvirtconnection.py b/lib/vdsm/libvirtconnection.py
index 5430c82..009f8b7 100644
--- a/lib/vdsm/libvirtconnection.py
+++ b/lib/vdsm/libvirtconnection.py
@@ -37,7 +37,8 @@
self.__thread = None
def start(self):
- assert not self.run
+ if self.run:
+ raise AssertionError("EventLoop is running")
self.__thread = threading.Thread(target=self.__run,
name="libvirtEventLoop")
self.__thread.setDaemon(True)
--
To view, visit http://gerrit.ovirt.org/34364
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Icf1564f81f4c1fbf77ccaff6d93c047a02d946da
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: vm: Remove useless volume size monitoring
......................................................................
vm: Remove useless volume size monitoring
We used to check drive volume size every 60 seconds but we do not use
the result of this check for anything. According old comments, the
result was used in the past for extending disks. Since 3.6 we are using
the capacity value from libvirt.
This check may block on storage apis for minutes when using NFS
unresponsive storage, blocking executor workers and harming unrelated
vms using other storage.
Change-Id: Ib1436c2968f3e408ce38a913c6ca3146a25a312d
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/config.py.in
M lib/vdsm/virt/periodic.py
M vdsm/virt/vm.py
M vdsm/virt/vmdevices/storage.py
4 files changed, 19 insertions(+), 89 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/01/59801/1
diff --git a/lib/vdsm/config.py.in b/lib/vdsm/config.py.in
index 8bcb09c..ad2f8c7 100644
--- a/lib/vdsm/config.py.in
+++ b/lib/vdsm/config.py.in
@@ -287,9 +287,6 @@
'volume_utilization_percent, set the free space limit. Use higher '
'values to extend in bigger chunks.'),
- ('vol_size_sample_interval', '60',
- 'How often should the volume size be checked (seconds).'),
-
('scsi_rescan_maximal_timeout', '30',
'The maximal number of seconds to wait for scsi scan to return.'),
diff --git a/lib/vdsm/virt/periodic.py b/lib/vdsm/virt/periodic.py
index 8ed5d67..72f33a9 100644
--- a/lib/vdsm/virt/periodic.py
+++ b/lib/vdsm/virt/periodic.py
@@ -71,12 +71,6 @@
return Operation(disp, period, scheduler)
_operations = [
- # needs dispatching becuse updating the volume stats needs the
- # access the storage, thus can block.
- per_vm_operation(
- UpdateVolumes,
- config.getint('irs', 'vol_size_sample_interval')),
-
# needs dispatching becuse access FS and libvirt data
per_vm_operation(
NumaInfoMonitor,
@@ -316,22 +310,6 @@
return '<%s vm=%s at 0x%x>' % (
self.__class__.__name__, self._vm.id, id(self)
)
-
-
-class UpdateVolumes(_RunnableOnVm):
-
- @property
- def required(self):
- return (super(UpdateVolumes, self).required and
- # Avoid queries from storage during recovery process
- self._vm.isDisksStatsCollectionEnabled())
-
- def _execute(self):
- for drive in self._vm.getDiskDevices():
- # TODO: If this block (it is actually possible?)
- # we must make sure we don't overwrite good data
- # with stale old data.
- self._vm.updateDriveVolume(drive)
class NumaInfoMonitor(_RunnableOnVm):
diff --git a/vdsm/virt/vm.py b/vdsm/virt/vm.py
index b5eec87..db2bec0 100644
--- a/vdsm/virt/vm.py
+++ b/vdsm/virt/vm.py
@@ -378,15 +378,6 @@
if 'device' not in drv:
drv['device'] = 'disk'
- if drv['device'] == 'disk':
- volsize = self._getVolumeSize(drv['domainID'], drv['poolID'],
- drv['imageID'], drv['volumeID'])
- drv['truesize'] = str(volsize.truesize)
- drv['apparentsize'] = str(volsize.apparentsize)
- else:
- drv['truesize'] = 0
- drv['apparentsize'] = 0
-
def __legacyDrives(self):
"""
Backward compatibility for qa scripts that specify direct paths.
@@ -398,8 +389,7 @@
if path:
legacies.append({'type': hwclass.DISK,
'device': 'disk', 'path': path,
- 'iface': 'ide', 'index': index,
- 'truesize': 0})
+ 'iface': 'ide', 'index': index})
return legacies
def __removableDrives(self):
@@ -408,8 +398,7 @@
'device': 'cdrom',
'iface': vmdevices.storage.DEFAULT_INTERFACE_FOR_ARCH[self.arch],
'path': self.conf.get('cdrom', ''),
- 'index': 2,
- 'truesize': 0}]
+ 'index': 2}]
floppyPath = self.conf.get('floppy')
if floppyPath:
removables.append({
@@ -417,8 +406,7 @@
'device': 'floppy',
'path': floppyPath,
'iface': 'fdc',
- 'index': 0,
- 'truesize': 0})
+ 'index': 0})
return removables
def _devMapFromDevSpecMap(self, dev_spec_map):
@@ -999,10 +987,9 @@
for drive, volumeID, capacity, alloc, physical in extend:
self.log.info(
- "Requesting extension for volume %s on domain %s (apparent: "
- "%s, capacity: %s, allocated: %s, physical: %s)",
- volumeID, drive.domainID, drive.apparentsize, capacity,
- alloc, physical)
+ "Requesting extension for volume %s on domain %s (capacity: "
+ "%s, allocated: %s, physical: %s)",
+ volumeID, drive.domainID, capacity, alloc, physical)
self.extendDriveVolume(drive, volumeID, physical, capacity)
return len(extend) > 0
@@ -1044,8 +1031,6 @@
raise RuntimeError(
"Volume extension failed for %s (domainID: %s, volumeID: %s)" %
(volInfo['name'], volInfo['domainID'], volInfo['volumeID']))
-
- return volSize
def __afterReplicaExtension(self, volInfo):
self.__verifyVolumeExtension(volInfo)
@@ -1093,14 +1078,7 @@
def __afterVolumeExtension(self, volInfo):
# Check if the extension succeeded. On failure an exception is raised
# TODO: Report failure to the engine.
- volSize = self.__verifyVolumeExtension(volInfo)
-
- # Only update apparentsize and truesize if we've resized the leaf
- if not volInfo['internal']:
- vmDrive = self._findDriveByName(volInfo['name'])
- vmDrive.apparentsize = volSize.apparentsize
- vmDrive.truesize = volSize.truesize
-
+ self.__verifyVolumeExtension(volInfo)
try:
self.cont()
except libvirt.libvirtError:
@@ -3235,22 +3213,6 @@
return device
raise LookupError("No such disk %r" % name)
- def updateDriveVolume(self, vmDrive):
- if not vmDrive.device == 'disk' or not isVdsmImage(vmDrive):
- return
-
- try:
- volSize = self._getVolumeSize(
- vmDrive.domainID, vmDrive.poolID, vmDrive.imageID,
- vmDrive.volumeID)
- except StorageUnavailableError as e:
- self.log.error("Unable to update drive %s volume size: %s",
- vmDrive.name, e)
- return
-
- vmDrive.truesize = volSize.truesize
- vmDrive.apparentsize = volSize.apparentsize
-
def updateDriveParameters(self, driveParams):
"""Update the drive with the new volume information"""
@@ -3259,7 +3221,6 @@
if vmDrive.name == driveParams["name"]:
for k, v in driveParams.iteritems():
setattr(vmDrive, k, v)
- self.updateDriveVolume(vmDrive)
break
else:
self.log.error("Unable to update the drive object for: %s",
@@ -3499,11 +3460,8 @@
# see the XML even with 'info' as default level.
self.log.info(snapxml)
- # We need to stop the collection of the stats for two reasons, one
- # is to prevent spurious libvirt errors about missing drive paths
- # (since we're changing them), and also to prevent to trigger a drive
- # extension for the new volume with the apparent size of the old one
- # (the apparentsize is updated as last step in updateDriveParameters)
+ # Prevent spurious libvirt errors about missing drive paths (since
+ # we're changing them).
self.stopDisksStatsCollection()
try:
diff --git a/vdsm/virt/vmdevices/storage.py b/vdsm/virt/vmdevices/storage.py
index 78bf00c..eb8b2ae 100644
--- a/vdsm/virt/vmdevices/storage.py
+++ b/vdsm/virt/vmdevices/storage.py
@@ -56,13 +56,14 @@
class Drive(Base):
- __slots__ = ('iface', '_path', 'readonly', 'bootOrder', 'domainID',
- 'poolID', 'imageID', 'UUID', 'volumeID', 'format',
- 'propagateErrors', 'address', 'apparentsize', 'volumeInfo',
- 'index', 'name', 'optional', 'shared', 'truesize',
- 'volumeChain', 'baseVolumeID', 'serial', 'reqsize', 'cache',
- '_blockDev', 'extSharedState', 'drv', 'sgio', 'GUID',
- 'diskReplicate', '_diskType', 'hosts', 'protocol', 'auth')
+ __slots__ = (
+ 'iface', '_path', 'readonly', 'bootOrder', 'domainID', 'poolID',
+ 'imageID', 'UUID', 'volumeID', 'format', 'propagateErrors', 'address',
+ 'volumeInfo', 'index', 'name', 'optional', 'shared', 'volumeChain',
+ 'baseVolumeID', 'serial', 'reqsize', 'cache', '_blockDev',
+ 'extSharedState', 'drv', 'sgio', 'GUID', 'diskReplicate', '_diskType',
+ 'hosts', 'protocol', 'auth'
+ )
VOLWM_CHUNK_SIZE = (config.getint('irs', 'volume_utilization_chunk_mb') *
constants.MEGAB)
VOLWM_FREE_PCT = 100 - config.getint('irs', 'volume_utilization_percent')
@@ -165,8 +166,6 @@
self.device = getattr(self, 'device', 'disk')
# Keep sizes as int
self.reqsize = int(kwargs.get('reqsize', '0')) # Backward compatible
- self.truesize = int(kwargs.get('truesize', '0'))
- self.apparentsize = int(kwargs.get('apparentsize', '0'))
self.name = makeName(self.iface, self.index)
self.cache = config.get('vars', 'qemu_drive_cache')
@@ -247,10 +246,8 @@
Returns the next volume size in bytes. This value is based on the
volExtensionChunk property and it's the size that should be requested
for the next LV extension. curSize is the current size of the volume
- to be extended. For the leaf volume curSize == self.apparentsize.
- For internal volumes it is discovered by calling irs.getVolumeSize().
- capacity is the maximum size of the volume. It can be discovered using
- libvirt.virDomain.blockInfo() or qemuimg.info().
+ to be extended. capacity is the maximum size of the volume. It can be
+ discovered using libvirt.virDomain.blockInfo() or qemuimg.info().
"""
nextSize = utils.round(curSize + self.volExtensionChunk,
constants.MEGAB)
--
To view, visit https://gerrit.ovirt.org/59801
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib1436c2968f3e408ce38a913c6ca3146a25a312d
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>
Nir Soffer has uploaded a new change for review.
Change subject: cache: Add caching decorator with invalidation
......................................................................
cache: Add caching decorator with invalidation
The new cache.memoized extends utils.memoized, adding invalidation
support.
Features added:
- An optional "validate" argument. This is a callable invoked each time
the memoized function is called. When the callable returns False, the
cache is invalidated.
- Memoized functions have an "invalidate" method, used to invalidate the
cache during testing.
- file_validator - invalidates the cache when a file changes.
Example usage:
from vdsm.cache import memoized, file_validator
@memoized(file_validator('/bigfile'))
def parse_bigfile():
# Expensive code processing '/bigfile' contents
Change-Id: I6dd8fb29d94286e3e3a3e29b8218501cbdc5c018
Signed-off-by: Nir Soffer <nsoffer(a)redhat.com>
---
M lib/vdsm/Makefile.am
A lib/vdsm/cache.py
M tests/Makefile.am
A tests/cacheTests.py
4 files changed, 366 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/09/34709/1
diff --git a/lib/vdsm/Makefile.am b/lib/vdsm/Makefile.am
index b862e71..6f0040d 100644
--- a/lib/vdsm/Makefile.am
+++ b/lib/vdsm/Makefile.am
@@ -23,6 +23,7 @@
dist_vdsmpylib_PYTHON = \
__init__.py \
+ cache.py \
compat.py \
define.py \
exception.py \
diff --git a/lib/vdsm/cache.py b/lib/vdsm/cache.py
new file mode 100644
index 0000000..9806e40
--- /dev/null
+++ b/lib/vdsm/cache.py
@@ -0,0 +1,98 @@
+#
+# Copyright 2014 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 errno
+import os
+import functools
+
+
+def memoized(validate=None):
+ """
+ Return a caching decorator supporting invalidation.
+
+ The decorator accepts an optional validate callable, called each time the
+ memoized function is called. If the validate callable return True, the
+ memoized function will use the cache. If the validate callable return
+ False, the memoized cache is cleared.
+
+ The memoized function may accept multiple positional arguments. The
+ cache store the result for each combination of arguments. Functions with
+ kwargs are not supported.
+
+ Memoized functions have an "invalidate" method, used to invalidate the
+ memoized cache during testing.
+
+ To invalidate the cache when a file changes, use the file_validator from
+ this module.
+
+ Example usage:
+
+ from vdsm.cache import memoized, file_validator
+
+ @memoized(file_validator('/bigfile'))
+ def parse_bigfile():
+ # Expensive code processing '/bigfile' contents
+
+ """
+ def decorator(f):
+ cache = {}
+
+ @functools.wraps(f)
+ def wrapper(*args):
+ if validate is not None and not validate():
+ cache.clear()
+ try:
+ value = cache[args]
+ except KeyError:
+ value = cache[args] = f(*args)
+ return value
+
+ wrapper.invalidate = cache.clear
+ return wrapper
+
+ return decorator
+
+
+class file_validator(object):
+ """
+ I'm a validator returning False when a file has changed since the last
+ validation.
+ """
+
+ UNKNOWN = 0
+ MISSING = 1
+
+ def __init__(self, path):
+ self.path = path
+ self.stats = self.UNKNOWN
+
+ def __call__(self):
+ try:
+ stats = os.stat(self.path)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ stats = self.MISSING
+ else:
+ stats = stats.st_ino, stats.st_size, stats.st_mtime
+ if stats != self.stats:
+ self.stats = stats
+ return False
+ return True
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 36a1cdd..6fa7e64 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -26,6 +26,7 @@
alignmentScanTests.py \
blocksdTests.py \
bridgeTests.py \
+ cacheTests.py \
cPopenTests.py \
capsTests.py \
clientifTests.py \
diff --git a/tests/cacheTests.py b/tests/cacheTests.py
new file mode 100644
index 0000000..8927b39
--- /dev/null
+++ b/tests/cacheTests.py
@@ -0,0 +1,266 @@
+#
+# Copyright 2014 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
+from vdsm.cache import memoized
+from vdsm.cache import file_validator
+from testlib import VdsmTestCase
+from testlib import namedTemporaryDir
+
+
+class Validator(object):
+ """ I'm a callable returning a boolean value (self.valid) """
+
+ def __init__(self):
+ self.valid = True
+ self.count = 0
+
+ def __call__(self):
+ self.count += 1
+ return self.valid
+
+
+class Accessor(object):
+ """ I'm recording how many times a dict was accessed. """
+
+ def __init__(self, d):
+ self.d = d
+ self.count = 0
+
+ def get(self, key):
+ self.count += 1
+ return self.d[key]
+
+
+class MemoizedTests(VdsmTestCase):
+
+ def setUp(self):
+ self.values = {'a': 0, 'b': 10, ('a',): 20, ('a', 'b'): 30}
+
+ def test_no_args(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(key):
+ return accessor.get(key)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+
+ def test_validation(self):
+ accessor = Accessor(self.values)
+ validator = Validator()
+
+ @memoized(validator)
+ def func(key):
+ return accessor.get(key)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 4)
+
+ # Values has changed
+ self.values['a'] += 1
+ self.values['b'] += 1
+
+ # Next call should clear the cache
+ validator.valid = False
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 3)
+ self.assertEqual(validator.count, 5)
+
+ # Next call should add next value to cache
+ validator.valid = True
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 4)
+ self.assertEqual(validator.count, 6)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(func('b'), self.values['b'])
+ self.assertEqual(accessor.count, 4)
+ self.assertEqual(validator.count, 8)
+
+ def test_raise_errors_in_memoized_func(self):
+ accessor = Accessor(self.values)
+ validator = Validator()
+
+ @memoized(validator)
+ def func(key):
+ return accessor.get(key)
+
+ # First run should fail, second shold fill the cache
+ self.assertRaises(KeyError, func, 'no such key')
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 2)
+ self.assertEqual(validator.count, 2)
+
+ def test_multiple_args(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(*args):
+ return accessor.get(args)
+
+ # Fill the cache
+ self.assertEqual(func('a'), self.values[('a',)])
+ self.assertEqual(func('a', 'b'), self.values[('a', 'b')])
+ self.assertEqual(accessor.count, 2)
+
+ # Values served now from the cache
+ self.assertEqual(func('a'), self.values[('a',)])
+ self.assertEqual(func('a', 'b'), self.values[('a', 'b')])
+ self.assertEqual(accessor.count, 2)
+
+ def test_kwargs_not_supported(self):
+ @memoized()
+ def func(a=None, b=None):
+ pass
+ self.assertRaises(TypeError, func, a=1, b=2)
+
+ def test_invalidate(self):
+ accessor = Accessor(self.values)
+
+ @memoized()
+ def func(key):
+ return accessor.get(key)
+
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 1)
+
+ func.invalidate()
+
+ self.assertEqual(func('a'), self.values['a'])
+ self.assertEqual(accessor.count, 2)
+
+
+class FileValidatorTests(VdsmTestCase):
+
+ def test_no_file(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ # Must be False so memoise call the decorated function
+ self.assertEqual(validator(), False)
+
+ # Since file state did not change, must remain True
+ self.assertEqual(validator(), True)
+
+ def test_file_created(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ with open(path, 'w') as f:
+ f.write('data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_file_removed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+
+ with open(path, 'w') as f:
+ f.write('data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ os.unlink(path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_size_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ with open(path, 'w') as f:
+ f.write(data + ' new data')
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_mtime_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ # Fake timestamp change, as timestamp resolution may not be good
+ # enough when comparing changes during the test.
+ atime = mtime = os.path.getmtime(path) + 1
+ os.utime(path, (atime, mtime))
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ def test_ino_changed(self):
+ with namedTemporaryDir() as tempdir:
+ path = os.path.join(tempdir, 'data')
+ validator = file_validator(path)
+ data = 'old data'
+ with open(path, 'w') as f:
+ f.write(data)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
+
+ tmp = path + '.tmp'
+ with open(tmp, 'w') as f:
+ f.write(data)
+ os.rename(tmp, path)
+
+ self.assertEqual(validator(), False)
+ self.assertEqual(validator(), True)
--
To view, visit http://gerrit.ovirt.org/34709
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6dd8fb29d94286e3e3a3e29b8218501cbdc5c018
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer(a)redhat.com>