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