Treat 'No space left on device' error as fatal

Fail without retries when Errno 28 - "No space left
on device" error is encountered.

Closes-Bug: #2094854
Change-Id: Ie84b422916ddc02f2474164fe3da083324ef4824
This commit is contained in:
cid
2025-01-16 23:03:06 +01:00
parent 6dceb33209
commit c222626b01
4 changed files with 57 additions and 1 deletions

View File

@@ -156,6 +156,11 @@ class ImageDownloadError(RESTError):
super(ImageDownloadError, self).__init__(details)
class ImageDownloadOutofSpaceError(ImageDownloadError):
"""Raised when an image download fails due to insufficient storage."""
pass
class ImageChecksumError(RESTError):
"""Error raised when an image fails to verify against its checksum."""

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import hashlib
import os
import re
@@ -679,6 +680,8 @@ def _download_image(image_info):
:param image_info: Image information dictionary.
:raises: ImageDownloadError if the image download fails for any reason.
:raises: ImageDownloadOutofSpaceError if the image download fails
due to insufficient storage space.
:raises: ImageChecksumError if the downloaded image's checksum does not
match the one reported in image_info.
"""
@@ -691,12 +694,24 @@ def _download_image(image_info):
with open(image_location, 'wb') as f:
try:
for chunk in image_download:
f.write(chunk)
try:
f.write(chunk)
except OSError as e:
if e.errno == errno.ENOSPC:
msg = ('Unable to write image to {}. Error: {}'
).format(image_location, str(e))
raise errors.ImageDownloadOutofSpaceError(
image_info['id'], msg)
raise
except Exception as e:
if isinstance(e, errors.ImageDownloadOutofSpaceError):
raise
msg = 'Unable to write image to {}. Error: {}'.format(
image_location, str(e))
raise errors.ImageDownloadError(image_info['id'], msg)
image_download.verify_image(image_location)
except errors.ImageDownloadOutofSpaceError:
raise
except (errors.ImageDownloadError,
errors.ImageChecksumError) as e:
if attempt == CONF.image_download_connection_retries:

View File

@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import errno
import os
import tempfile
import time
@@ -1775,6 +1776,36 @@ class TestImageDownload(base.IronicAgentTest):
sleep_mock.assert_called_with(10)
self.assertEqual(2, sleep_mock.call_count)
@mock.patch('time.sleep', autospec=True)
def test_download_image_no_space_error_fatal(self, sleep_mock,
requests_mock, hash_mock):
content = ['SpongeBob', 'SquarePants']
response = requests_mock.return_value
response.status_code = 200
response.iter_content.return_value = content
image_info = _build_fake_image_info()
hash_mock.return_value.hexdigest.return_value = image_info[
'os_hash_value']
mock_open = mock.mock_open()
mock_file = mock_open.return_value.__enter__.return_value
mock_file.write.side_effect = OSError(errno.ENOSPC,
'No space left on device')
with mock.patch('builtins.open', mock_open):
self.assertRaises(
errors.ImageDownloadOutofSpaceError,
standby._download_image,
image_info
)
requests_mock.assert_called_once_with(image_info['urls'][0],
cert=None, verify=True,
stream=True, proxies={},
timeout=60)
sleep_mock.assert_not_called()
@mock.patch.object(standby.LOG, 'warning', autospec=True)
def test_download_image_and_checksum(self, warn_mock, requests_mock,
hash_mock):

View File

@@ -0,0 +1,5 @@
---
fixes:
- |
Fail fast when encountering disk space errors during image
downloads instead of attempting futile retries.