Add retry loop for docker pull
The prepare command also needs a retry loop for docker pull in order to accommodate with the not so infrequent I/O failures. Change-Id: I14d5ef226808e8bf2e983b74c9918ea4d61a4aa1 Related-Bug: #1715136
This commit is contained in:
parent
6350a6651d
commit
2ff0842fcb
@ -19,6 +19,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import netifaces
|
import netifaces
|
||||||
import six
|
import six
|
||||||
|
import time
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from docker import APIClient as Client
|
from docker import APIClient as Client
|
||||||
@ -119,7 +120,7 @@ class DockerImageUploader(ImageUploader):
|
|||||||
else:
|
else:
|
||||||
repo = image
|
repo = image
|
||||||
|
|
||||||
self._pull(dockerc, repo, tag=tag)
|
self._pull_retry(dockerc, repo, tag=tag)
|
||||||
|
|
||||||
full_image = repo + ':' + tag
|
full_image = repo + ':' + tag
|
||||||
new_repo = push_destination + '/' + repo.partition('/')[2]
|
new_repo = push_destination + '/' + repo.partition('/')[2]
|
||||||
@ -138,9 +139,22 @@ class DockerImageUploader(ImageUploader):
|
|||||||
for line in dockerc.pull(image, tag=tag, stream=True):
|
for line in dockerc.pull(image, tag=tag, stream=True):
|
||||||
status = json.loads(line)
|
status = json.loads(line)
|
||||||
if 'error' in status:
|
if 'error' in status:
|
||||||
raise ImageUploaderException('Could not pull image: %s' %
|
self.logger.warning('docker pull failed: %s' % status['error'])
|
||||||
status['error'])
|
return 1
|
||||||
self.logger.debug(status.get('status'))
|
self.logger.debug(status.get('status'))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _pull_retry(self, dockerc, image, tag=None):
|
||||||
|
retval = -1
|
||||||
|
count = 0
|
||||||
|
while retval != 0:
|
||||||
|
if count >= 5:
|
||||||
|
raise ImageUploaderException('Could not pull image %s' % image)
|
||||||
|
count += 1
|
||||||
|
retval = self._pull(dockerc, image, tag)
|
||||||
|
if retval != 0:
|
||||||
|
time.sleep(3)
|
||||||
|
self.logger.warning('retrying pulling image: %s' % image)
|
||||||
|
|
||||||
def discover_image_tag(self, image, tag_from_label=None):
|
def discover_image_tag(self, image, tag_from_label=None):
|
||||||
dockerc = Client(base_url='unix://var/run/docker.sock', version='auto')
|
dockerc = Client(base_url='unix://var/run/docker.sock', version='auto')
|
||||||
@ -150,7 +164,7 @@ class DockerImageUploader(ImageUploader):
|
|||||||
tag = 'latest'
|
tag = 'latest'
|
||||||
image = '%s:%s' % (image_name, tag)
|
image = '%s:%s' % (image_name, tag)
|
||||||
|
|
||||||
self._pull(dockerc, image)
|
self._pull_retry(dockerc, image)
|
||||||
i = dockerc.inspect_image(image)
|
i = dockerc.inspect_image(image)
|
||||||
labels = i['Config']['Labels']
|
labels = i['Config']['Labels']
|
||||||
|
|
||||||
@ -171,5 +185,5 @@ class DockerImageUploader(ImageUploader):
|
|||||||
# confirm the tag exists by pulling it, which should be fast
|
# confirm the tag exists by pulling it, which should be fast
|
||||||
# because that image has just been pulled
|
# because that image has just been pulled
|
||||||
versioned_image = '%s:%s' % (image_name, tag_label)
|
versioned_image = '%s:%s' % (image_name, tag_label)
|
||||||
self._pull(dockerc, versioned_image)
|
self._pull_retry(dockerc, versioned_image)
|
||||||
return tag_label
|
return tag_label
|
||||||
|
@ -234,26 +234,41 @@ class TestDockerImageUploader(base.TestCase):
|
|||||||
self.assertRaises(ImageUploaderException,
|
self.assertRaises(ImageUploaderException,
|
||||||
self.uploader.discover_image_tag, image, 'foo')
|
self.uploader.discover_image_tag, image, 'foo')
|
||||||
|
|
||||||
def test_discover_image_tag_missing_tag(self):
|
@mock.patch('time.sleep')
|
||||||
image = 'docker.io/tripleoupstream/heat-docker-agents-centos:latest'
|
def test_pull_retry(self, sleep_mock):
|
||||||
vimage = 'docker.io/tripleoupstream/heat-docker-agents-centos:1.2.3'
|
image = 'docker.io/tripleoupstream/heat-docker-agents-centos'
|
||||||
|
|
||||||
dockerc = self.dockermock.return_value
|
dockerc = self.dockermock.return_value
|
||||||
dockerc.pull.side_effect = [
|
dockerc.pull.side_effect = [
|
||||||
['{"status": "done"}'], # First pull, :latest
|
['{"error": "ouch"}'],
|
||||||
['{"error": "ouch"}'], # Second pull, :1.2.3
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"status": "done"}']
|
||||||
]
|
]
|
||||||
dockerc.inspect_image.return_value = {
|
self.uploader._pull_retry(dockerc, image)
|
||||||
'Config': {'Labels': {'image-version': '1.2.3'}}
|
|
||||||
}
|
|
||||||
self.assertRaises(ImageUploaderException,
|
|
||||||
self.uploader.discover_image_tag, image,
|
|
||||||
'image-version')
|
|
||||||
|
|
||||||
self.dockermock.assert_called_once_with(
|
|
||||||
base_url='unix://var/run/docker.sock', version='auto')
|
|
||||||
|
|
||||||
|
self.assertEqual(sleep_mock.call_count, 4)
|
||||||
dockerc.pull.assert_has_calls([
|
dockerc.pull.assert_has_calls([
|
||||||
mock.call(image, tag=None, stream=True),
|
mock.call(image, tag=None, stream=True)
|
||||||
mock.call(vimage, tag=None, stream=True),
|
])
|
||||||
|
|
||||||
|
@mock.patch('time.sleep')
|
||||||
|
def test_pull_retry_failure(self, sleep_mock):
|
||||||
|
image = 'docker.io/tripleoupstream/heat-docker-agents-centos'
|
||||||
|
|
||||||
|
dockerc = self.dockermock.return_value
|
||||||
|
dockerc.pull.side_effect = [
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
['{"error": "ouch"}'],
|
||||||
|
]
|
||||||
|
self.assertRaises(ImageUploaderException,
|
||||||
|
self.uploader._pull_retry, dockerc, image)
|
||||||
|
|
||||||
|
self.assertEqual(sleep_mock.call_count, 5)
|
||||||
|
dockerc.pull.assert_has_calls([
|
||||||
|
mock.call(image, tag=None, stream=True)
|
||||||
])
|
])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user