diff --git a/ironic_python_agent/errors.py b/ironic_python_agent/errors.py index 0a678c29f..58ac4245c 100644 --- a/ironic_python_agent/errors.py +++ b/ironic_python_agent/errors.py @@ -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.""" diff --git a/ironic_python_agent/extensions/standby.py b/ironic_python_agent/extensions/standby.py index 0f0b66db5..e8731c671 100644 --- a/ironic_python_agent/extensions/standby.py +++ b/ironic_python_agent/extensions/standby.py @@ -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: diff --git a/ironic_python_agent/tests/unit/extensions/test_standby.py b/ironic_python_agent/tests/unit/extensions/test_standby.py index 60199d393..5fc7b85a2 100644 --- a/ironic_python_agent/tests/unit/extensions/test_standby.py +++ b/ironic_python_agent/tests/unit/extensions/test_standby.py @@ -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): diff --git a/releasenotes/notes/no-retry-disk-space-errors-a24c29b336fd11d9.yaml b/releasenotes/notes/no-retry-disk-space-errors-a24c29b336fd11d9.yaml new file mode 100644 index 000000000..3632d4fa8 --- /dev/null +++ b/releasenotes/notes/no-retry-disk-space-errors-a24c29b336fd11d9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fail fast when encountering disk space errors during image + downloads instead of attempting futile retries.