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:
@@ -156,6 +156,11 @@ class ImageDownloadError(RESTError):
|
|||||||
super(ImageDownloadError, self).__init__(details)
|
super(ImageDownloadError, self).__init__(details)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageDownloadOutofSpaceError(ImageDownloadError):
|
||||||
|
"""Raised when an image download fails due to insufficient storage."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImageChecksumError(RESTError):
|
class ImageChecksumError(RESTError):
|
||||||
"""Error raised when an image fails to verify against its checksum."""
|
"""Error raised when an image fails to verify against its checksum."""
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -679,6 +680,8 @@ def _download_image(image_info):
|
|||||||
|
|
||||||
:param image_info: Image information dictionary.
|
:param image_info: Image information dictionary.
|
||||||
:raises: ImageDownloadError if the image download fails for any reason.
|
: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
|
:raises: ImageChecksumError if the downloaded image's checksum does not
|
||||||
match the one reported in image_info.
|
match the one reported in image_info.
|
||||||
"""
|
"""
|
||||||
@@ -691,12 +694,24 @@ def _download_image(image_info):
|
|||||||
with open(image_location, 'wb') as f:
|
with open(image_location, 'wb') as f:
|
||||||
try:
|
try:
|
||||||
for chunk in image_download:
|
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:
|
except Exception as e:
|
||||||
|
if isinstance(e, errors.ImageDownloadOutofSpaceError):
|
||||||
|
raise
|
||||||
msg = 'Unable to write image to {}. Error: {}'.format(
|
msg = 'Unable to write image to {}. Error: {}'.format(
|
||||||
image_location, str(e))
|
image_location, str(e))
|
||||||
raise errors.ImageDownloadError(image_info['id'], msg)
|
raise errors.ImageDownloadError(image_info['id'], msg)
|
||||||
image_download.verify_image(image_location)
|
image_download.verify_image(image_location)
|
||||||
|
except errors.ImageDownloadOutofSpaceError:
|
||||||
|
raise
|
||||||
except (errors.ImageDownloadError,
|
except (errors.ImageDownloadError,
|
||||||
errors.ImageChecksumError) as e:
|
errors.ImageChecksumError) as e:
|
||||||
if attempt == CONF.image_download_connection_retries:
|
if attempt == CONF.image_download_connection_retries:
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import errno
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@@ -1775,6 +1776,36 @@ class TestImageDownload(base.IronicAgentTest):
|
|||||||
sleep_mock.assert_called_with(10)
|
sleep_mock.assert_called_with(10)
|
||||||
self.assertEqual(2, sleep_mock.call_count)
|
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)
|
@mock.patch.object(standby.LOG, 'warning', autospec=True)
|
||||||
def test_download_image_and_checksum(self, warn_mock, requests_mock,
|
def test_download_image_and_checksum(self, warn_mock, requests_mock,
|
||||||
hash_mock):
|
hash_mock):
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fail fast when encountering disk space errors during image
|
||||||
|
downloads instead of attempting futile retries.
|
Reference in New Issue
Block a user