Fix cache image hard link between different file systems

When caching an image between different file systems, the hard link
operation would fail. This is fixed by falling back to a copy
operation.

Assisted-By: claude-4-sonnet
Change-Id: Id1eced2e0a30044b0da7dd5f4f2dedc50a5297b6
Signed-off-by: Riccardo Pittau <elfosardo@gmail.com>
(cherry picked from commit 685dbeb356)
This commit is contained in:
Riccardo Pittau
2025-09-24 15:09:33 +02:00
parent 1394a234b1
commit 4db2530d3a
3 changed files with 47 additions and 1 deletions

View File

@@ -19,6 +19,7 @@ Utility for caching master images.
"""
import os
import shutil
import tempfile
import threading
import time
@@ -167,7 +168,17 @@ class ImageCache(object):
if cache_up_to_date:
# NOTE(dtantsur): ensure we're not in the middle of clean up
with lockutils.lock('master_image'):
os.link(master_path, dest_path)
try:
os.link(master_path, dest_path)
except OSError as exc:
LOG.debug(
"Could not hardlink image file %(image)s to "
"the cache location %(dest_path)s (will copy it "
"over): %(error)s", {
'image': master_path,
'dest_path': dest_path,
'error': exc})
shutil.copyfile(master_path, dest_path)
LOG.debug("Master cache hit for image %(href)s",
{'href': href})
return

View File

@@ -18,6 +18,7 @@
import datetime
import os
import shutil
import tempfile
import time
from unittest import mock
@@ -238,6 +239,34 @@ class TestImageCacheFetch(BaseTest):
mock_clean_up.assert_not_called()
mock_image_service.assert_not_called()
@mock.patch.object(shutil, 'copyfile', autospec=True)
@mock.patch.object(os, 'link', autospec=True)
@mock.patch.object(image_cache, '_delete_dest_path_if_stale',
return_value=False, autospec=True)
@mock.patch.object(image_cache, '_delete_master_path_if_stale',
return_value=True, autospec=True)
def test_fetch_image_hardlink_fails_fallback_to_copy(
self, mock_cache_upd, mock_dest_upd, mock_link, mock_copyfile,
mock_download, mock_clean_up, mock_image_service):
mock_link.side_effect = OSError("Invalid cross-device link")
self.cache.fetch_image(self.uuid, self.dest_path)
mock_link.assert_called_once_with(self.master_path, self.dest_path)
mock_copyfile.assert_called_once_with(self.master_path, self.dest_path)
mock_cache_upd.assert_called_once_with(
self.master_path, self.uuid,
mock_image_service.return_value.show.return_value)
mock_dest_upd.assert_called_once_with(self.master_path, self.dest_path)
mock_download.assert_not_called()
mock_clean_up.assert_not_called()
mock_image_service.assert_called_once_with(self.uuid, context=None)
mock_image_service.return_value.show.assert_called_once_with(self.uuid)
@mock.patch.object(image_cache, '_fetch', autospec=True)
class TestImageCacheDownload(BaseTest):

View File

@@ -0,0 +1,6 @@
---
fixes:
- |
When caching an image between different file systems, the hard link
operation would fail. This is fixed by falling back to a copy
operation.