Change in vdsm[master]: fileUtils: Add atomic_symlink utility
nsoffer at redhat.com
nsoffer at redhat.com
Mon Feb 29 21:22:31 UTC 2016
Nir Soffer has uploaded a new change for review.
Change subject: fileUtils: Add atomic_symlink utility
......................................................................
fileUtils: Add atomic_symlink utility
This helper function creates or updates a symlink atomically. If the
symlink exists but links to different target, it is replaced atomically
with a link to the correct target.
We use this logic now for linking storage domain, and we want to use the
same logic for creating or updating the image rundir links
(/run/vdsm/storage/sd_uuid/img_uuid)
If the process is killed while creating a link, it may leave temporary
link (link.tmp). This file will be removed in the next time a link is
created.
Change-Id: I931eca7a25c7b10344fce78b274d633d73eb082d
Signed-off-by: Nir Soffer <nsoffer at redhat.com>
---
M tests/fileUtilTests.py
M vdsm/storage/fileUtils.py
2 files changed, 89 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/80/53680/1
diff --git a/tests/fileUtilTests.py b/tests/fileUtilTests.py
index add36c2..8c6081a 100644
--- a/tests/fileUtilTests.py
+++ b/tests/fileUtilTests.py
@@ -226,3 +226,59 @@
fileUtils.copyUserModeToGroup(path)
self.assertEquals(os.stat(path).st_mode & self.MODE_MASK,
expectedMode)
+
+
+class TestAtomicSymlink(TestCaseBase):
+
+ def test_create_new(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ fileUtils.atomic_symlink(target, link)
+ self.assertEqual(os.readlink(link), target)
+ self.assertFalse(os.path.exists(link + ".tmp"))
+
+ def test_keep_current(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ fileUtils.atomic_symlink(target, link)
+ current = os.lstat(link)
+ fileUtils.atomic_symlink(target, link)
+ new = os.lstat(link)
+ self.assertEqual(current, new)
+ self.assertFalse(os.path.exists(link + ".tmp"))
+
+ def test_replace_stale(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ fileUtils.atomic_symlink("stale", link)
+ fileUtils.atomic_symlink(target, link)
+ self.assertEqual(os.readlink(link), target)
+ self.assertFalse(os.path.exists(link + ".tmp"))
+
+ def test_replace_stale_temporary_link(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ tmp_link = link + ".tmp"
+ fileUtils.atomic_symlink("stale", tmp_link)
+ fileUtils.atomic_symlink(target, link)
+ self.assertEqual(os.readlink(link), target)
+ self.assertFalse(os.path.exists(tmp_link))
+
+ def test_error_isfile(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ with open(link, 'w') as f:
+ f.write('data')
+ self.assertRaises(OSError, fileUtils.atomic_symlink, target, link)
+
+ def test_error_isdir(self):
+ with namedTemporaryDir() as tmpdir:
+ target = os.path.join(tmpdir, "target")
+ link = os.path.join(tmpdir, "link")
+ os.mkdir(link)
+ self.assertRaises(OSError, fileUtils.atomic_symlink, target, link)
diff --git a/vdsm/storage/fileUtils.py b/vdsm/storage/fileUtils.py
index 69aac2f..a3584ed 100644
--- a/vdsm/storage/fileUtils.py
+++ b/vdsm/storage/fileUtils.py
@@ -202,6 +202,39 @@
return open(path, mode)
+def atomic_symlink(target, name):
+ log.info("Linking %r to %r", target, name)
+ try:
+ current_target = os.readlink(name)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ else:
+ if current_target == target:
+ log.debug("link %r exists", name)
+ return
+ log.debug("Replacing stale link to %r", current_target)
+
+ tmp_name = name + ".tmp"
+ try:
+ os.symlink(target, tmp_name)
+ except OSError as e:
+ if e.errno != errno.EEXIST:
+ raise
+ log.debug("Removing stale temporary link %r", tmp_name)
+ os.unlink(tmp_name)
+ os.symlink(target, tmp_name)
+
+ try:
+ os.rename(tmp_name, name)
+ except:
+ try:
+ os.unlink(tmp_name)
+ except OSError as e:
+ log.error("Cannot remove temporary link %r: %s", tmp_name, e)
+ raise
+
+
class DirectFile(object):
def __init__(self, path, mode):
if "d" not in mode:
--
To view, visit https://gerrit.ovirt.org/53680
To unsubscribe, visit https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I931eca7a25c7b10344fce78b274d633d73eb082d
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer at redhat.com>
Gerrit-Reviewer: Adam Litke <alitke at redhat.com>
Gerrit-Reviewer: Dan Kenigsberg <danken at redhat.com>
Gerrit-Reviewer: Freddy Rolland <frolland at redhat.com>
Gerrit-Reviewer: Nir Soffer <nsoffer at redhat.com>
Gerrit-Reviewer: Simone Tiraboschi <stirabos at redhat.com>
Gerrit-Reviewer: gerrit-hooks <automation at ovirt.org>
More information about the vdsm-patches
mailing list