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 <nsof...@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 <nsof...@redhat.com>
Gerrit-Reviewer: Adam Litke <ali...@redhat.com>
Gerrit-Reviewer: Dan Kenigsberg <dan...@redhat.com>
Gerrit-Reviewer: Freddy Rolland <froll...@redhat.com>
Gerrit-Reviewer: Nir Soffer <nsof...@redhat.com>
Gerrit-Reviewer: Simone Tiraboschi <stira...@redhat.com>
Gerrit-Reviewer: gerrit-hooks <automat...@ovirt.org>
_______________________________________________
vdsm-patches mailing list
vdsm-patches@lists.fedorahosted.org
https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches

Reply via email to