Implements image_pull_policy
This patch implements image_pull_policy, which determines if the image should be pulled prior to starting the container. It can have following values: ifnotpresent: only pull the image if it does not already exist on the node. always: Always pull the image from repositery. never: never pull the image If image_pull_policy is not specified then: 1. If the tag is latest, defaults image_pull_policy is always. 2. Otherwise default is ifnotpresent Partial-Implements: blueprint image-pull-policy Change-Id: If2715d7724fd5336fb25d380f125e1485ca302d0
This commit is contained in:
parent
94d565ff0a
commit
2c6ad5a119
@ -311,3 +311,20 @@ class ImageSize(object):
|
|||||||
in both cases""")
|
in both cases""")
|
||||||
raise exception.InvalidValue(message=message, value=value,
|
raise exception.InvalidValue(message=message, value=value,
|
||||||
type=cls.type_name)
|
type=cls.type_name)
|
||||||
|
|
||||||
|
|
||||||
|
class EnumType(object):
|
||||||
|
type_name = 'EnumType'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, value, name=None, values=None):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if value.lower() not in set(values):
|
||||||
|
message = _(
|
||||||
|
"%(name)s should be one of: %(values)s") % {
|
||||||
|
'name': name,
|
||||||
|
'values': ', '.join(map(six.text_type, values))}
|
||||||
|
raise exception.InvalidValue(message)
|
||||||
|
else:
|
||||||
|
return value.lower()
|
||||||
|
@ -120,6 +120,13 @@ class Container(base.APIBase):
|
|||||||
'labels': {
|
'labels': {
|
||||||
'validate': types.Dict(types.String, types.String).validate,
|
'validate': types.Dict(types.String, types.String).validate,
|
||||||
},
|
},
|
||||||
|
'image_pull_policy': {
|
||||||
|
'validate': types.EnumType.validate,
|
||||||
|
'validate_args': {
|
||||||
|
'name': 'image_pull_policy',
|
||||||
|
'values': ['never', 'always', 'ifnotpresent']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@ -131,7 +138,7 @@ class Container(base.APIBase):
|
|||||||
container.unset_fields_except([
|
container.unset_fields_except([
|
||||||
'uuid', 'name', 'image', 'command', 'status', 'cpu', 'memory',
|
'uuid', 'name', 'image', 'command', 'status', 'cpu', 'memory',
|
||||||
'environment', 'task_state', 'workdir', 'ports', 'hostname',
|
'environment', 'task_state', 'workdir', 'ports', 'hostname',
|
||||||
'labels'])
|
'labels', 'image_pull_policy', 'status_reason'])
|
||||||
|
|
||||||
container.links = [link.Link.make_link(
|
container.links = [link.Link.make_link(
|
||||||
'self', url,
|
'self', url,
|
||||||
|
@ -141,3 +141,21 @@ def check_container_id(function):
|
|||||||
return function(*args, **kwargs)
|
return function(*args, **kwargs)
|
||||||
|
|
||||||
return decorated_function
|
return decorated_function
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_pull_policy(image_pull_policy, image_tag):
|
||||||
|
if not image_pull_policy:
|
||||||
|
if image_tag == 'latest':
|
||||||
|
image_pull_policy = 'always'
|
||||||
|
else:
|
||||||
|
image_pull_policy = 'ifnotpresent'
|
||||||
|
return image_pull_policy
|
||||||
|
|
||||||
|
|
||||||
|
def should_pull_image(image_pull_policy, present):
|
||||||
|
if image_pull_policy == 'never':
|
||||||
|
return False
|
||||||
|
if image_pull_policy == 'always' or \
|
||||||
|
(image_pull_policy == 'ifnotpresent' and not present):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@ -69,8 +69,11 @@ class Manager(object):
|
|||||||
container.task_state = fields.TaskState.IMAGE_PULLING
|
container.task_state = fields.TaskState.IMAGE_PULLING
|
||||||
container.save()
|
container.save()
|
||||||
repo, tag = utils.parse_image_name(container.image)
|
repo, tag = utils.parse_image_name(container.image)
|
||||||
|
image_pull_policy = utils.get_image_pull_policy(
|
||||||
|
container.image_pull_policy, tag)
|
||||||
try:
|
try:
|
||||||
image = image_driver.pull_image(context, repo, tag)
|
image = image_driver.pull_image(context, repo,
|
||||||
|
tag, image_pull_policy)
|
||||||
except exception.ImageNotFound as e:
|
except exception.ImageNotFound as e:
|
||||||
LOG.error(six.text_type(e))
|
LOG.error(six.text_type(e))
|
||||||
self._fail_container(container, six.text_type(e))
|
self._fail_container(container, six.text_type(e))
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""add image_pull_policy column
|
||||||
|
|
||||||
|
Revision ID: 43e1088c3389
|
||||||
|
Revises: c5565cbaa3de
|
||||||
|
Create Date: 2016-11-17 09:26:22.756296
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '43e1088c3389'
|
||||||
|
down_revision = 'c5565cbaa3de'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('container',
|
||||||
|
sa.Column('image_pull_policy', sa.Text(),
|
||||||
|
nullable=True))
|
@ -142,6 +142,7 @@ class Container(Base):
|
|||||||
ports = Column(JSONEncodedList)
|
ports = Column(JSONEncodedList)
|
||||||
hostname = Column(String(255))
|
hostname = Column(String(255))
|
||||||
labels = Column(JSONEncodedDict)
|
labels = Column(JSONEncodedDict)
|
||||||
|
image_pull_policy = Column(Text, nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Image(Base):
|
class Image(Base):
|
||||||
|
@ -21,6 +21,7 @@ from oslo_utils import excutils
|
|||||||
|
|
||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
from zun.common.i18n import _
|
from zun.common.i18n import _
|
||||||
|
from zun.common import utils
|
||||||
from zun.container.docker import utils as docker_utils
|
from zun.container.docker import utils as docker_utils
|
||||||
from zun.image import driver
|
from zun.image import driver
|
||||||
|
|
||||||
@ -32,6 +33,18 @@ class DockerDriver(driver.ContainerImageDriver):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DockerDriver, self).__init__()
|
super(DockerDriver, self).__init__()
|
||||||
|
|
||||||
|
def _search_image_on_host(self, repo, tag):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
image = repo + ":" + tag
|
||||||
|
LOG.debug('Inspecting image locally %s' % image)
|
||||||
|
try:
|
||||||
|
image_dict = docker.inspect_image(image)
|
||||||
|
if image_dict:
|
||||||
|
return {'image': repo, 'path': None}
|
||||||
|
except errors.NotFound:
|
||||||
|
LOG.debug('Image %s not found locally' % image)
|
||||||
|
return None
|
||||||
|
|
||||||
def _pull_image(self, repo, tag):
|
def _pull_image(self, repo, tag):
|
||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
for line in docker.pull(repo, tag=tag, stream=True):
|
for line in docker.pull(repo, tag=tag, stream=True):
|
||||||
@ -42,7 +55,17 @@ class DockerDriver(driver.ContainerImageDriver):
|
|||||||
else:
|
else:
|
||||||
raise exception.DockerError(error['message'])
|
raise exception.DockerError(error['message'])
|
||||||
|
|
||||||
def pull_image(self, context, repo, tag):
|
def pull_image(self, context, repo, tag, image_pull_policy):
|
||||||
|
image = self._search_image_on_host(repo, tag)
|
||||||
|
if not utils.should_pull_image(image_pull_policy, bool(image)):
|
||||||
|
if image:
|
||||||
|
LOG.debug('Image %s present locally' % repo)
|
||||||
|
return image
|
||||||
|
else:
|
||||||
|
message = _('Image %s not present with pull policy of Never'
|
||||||
|
) % repo
|
||||||
|
raise exception.ImageNotFound(message)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LOG.debug('Pulling image from docker %s,'
|
LOG.debug('Pulling image from docker %s,'
|
||||||
' context %s' % (repo, context))
|
' context %s' % (repo, context))
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@ -23,7 +22,6 @@ from zun.common.i18n import _
|
|||||||
from zun.common.i18n import _LE
|
from zun.common.i18n import _LE
|
||||||
from zun.common.i18n import _LI
|
from zun.common.i18n import _LI
|
||||||
import zun.conf
|
import zun.conf
|
||||||
from zun.image.glance import utils
|
|
||||||
|
|
||||||
CONF = zun.conf.CONF
|
CONF = zun.conf.CONF
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -57,35 +55,13 @@ def load_image_driver(image_driver=None):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def search_image_on_host(context, repo):
|
def pull_image(context, repo, tag, image_pull_policy):
|
||||||
LOG.debug('Searching for image %s locally' % repo)
|
|
||||||
CONF.import_opt('images_directory', 'zun.image.glance.driver',
|
|
||||||
group='glance')
|
|
||||||
images_directory = CONF.glance.images_directory
|
|
||||||
try:
|
|
||||||
# TODO(mkrai): Change this to search image entry in zun db
|
|
||||||
# after the image endpoint is merged.
|
|
||||||
image_meta = utils.find_image(context, repo)
|
|
||||||
except exception.ImageNotFound:
|
|
||||||
return None
|
|
||||||
if image_meta:
|
|
||||||
out_path = os.path.join(images_directory, image_meta.id + '.tar')
|
|
||||||
if os.path.isfile(out_path):
|
|
||||||
return {'image': repo, 'path': out_path}
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def pull_image(context, repo, tag):
|
|
||||||
image = search_image_on_host(context, repo)
|
|
||||||
if image:
|
|
||||||
LOG.debug('Found image %s locally.' % repo)
|
|
||||||
return image
|
|
||||||
image_driver_list = CONF.image_driver_list
|
image_driver_list = CONF.image_driver_list
|
||||||
for driver in image_driver_list:
|
for driver in image_driver_list:
|
||||||
try:
|
try:
|
||||||
image_driver = load_image_driver(driver)
|
image_driver = load_image_driver(driver)
|
||||||
image = image_driver.pull_image(context, repo, tag)
|
image = image_driver.pull_image(context, repo,
|
||||||
|
tag, image_pull_policy)
|
||||||
if image:
|
if image:
|
||||||
break
|
break
|
||||||
except exception.ImageNotFound:
|
except exception.ImageNotFound:
|
||||||
|
@ -20,6 +20,7 @@ from oslo_utils import fileutils
|
|||||||
|
|
||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
from zun.common.i18n import _
|
from zun.common.i18n import _
|
||||||
|
from zun.common import utils as common_utils
|
||||||
import zun.conf
|
import zun.conf
|
||||||
from zun.image import driver
|
from zun.image import driver
|
||||||
from zun.image.glance import utils
|
from zun.image.glance import utils
|
||||||
@ -34,9 +35,36 @@ class GlanceDriver(driver.ContainerImageDriver):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(GlanceDriver, self).__init__()
|
super(GlanceDriver, self).__init__()
|
||||||
|
|
||||||
def pull_image(self, context, repo, tag):
|
def _search_image_on_host(self, context, repo):
|
||||||
|
LOG.debug('Searching for image %s locally' % repo)
|
||||||
|
images_directory = CONF.glance.images_directory
|
||||||
|
try:
|
||||||
|
# TODO(mkrai): Change this to search image entry in zun db
|
||||||
|
# after the image endpoint is merged.
|
||||||
|
image_meta = utils.find_image(context, repo)
|
||||||
|
except exception.ImageNotFound:
|
||||||
|
return None
|
||||||
|
if image_meta:
|
||||||
|
out_path = os.path.join(images_directory,
|
||||||
|
image_meta.id + '.tar')
|
||||||
|
if os.path.isfile(out_path):
|
||||||
|
return {'image': repo, 'path': out_path}
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def pull_image(self, context, repo, tag, image_pull_policy):
|
||||||
# TODO(shubhams): glance driver does not handle tags
|
# TODO(shubhams): glance driver does not handle tags
|
||||||
# once metadata is stored in db then handle tags
|
# once metadata is stored in db then handle tags
|
||||||
|
image = self._search_image_on_host(context, repo)
|
||||||
|
if not common_utils.should_pull_image(image_pull_policy, bool(image)):
|
||||||
|
if image:
|
||||||
|
LOG.debug('Image %s present locally' % repo)
|
||||||
|
return image
|
||||||
|
else:
|
||||||
|
message = _('Image %s not present with pull policy of Never'
|
||||||
|
) % repo
|
||||||
|
raise exception.ImageNotFound(message)
|
||||||
|
|
||||||
LOG.debug('Pulling image from glance %s' % repo)
|
LOG.debug('Pulling image from glance %s' % repo)
|
||||||
try:
|
try:
|
||||||
glance = utils.create_glanceclient(context)
|
glance = utils.create_glanceclient(context)
|
||||||
|
@ -46,6 +46,7 @@ class Container(base.ZunPersistentObject, base.ZunObject,
|
|||||||
'ports': z_fields.ListOfIntegersField(nullable=True),
|
'ports': z_fields.ListOfIntegersField(nullable=True),
|
||||||
'hostname': fields.StringField(nullable=True),
|
'hostname': fields.StringField(nullable=True),
|
||||||
'labels': fields.DictOfStringsField(nullable=True),
|
'labels': fields.DictOfStringsField(nullable=True),
|
||||||
|
'image_pull_policy': fields.StringField(nullable=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -243,3 +243,26 @@ class TestTypes(test_base.BaseTestCase):
|
|||||||
expected_value = 4096
|
expected_value = 4096
|
||||||
value = types.ImageSize.validate(test_value)
|
value = types.ImageSize.validate(test_value)
|
||||||
self.assertEqual(value, expected_value)
|
self.assertEqual(value, expected_value)
|
||||||
|
|
||||||
|
def test_enum_type(self):
|
||||||
|
test_value = 'always'
|
||||||
|
self.assertEqual(test_value, types.EnumType.validate(
|
||||||
|
test_value, name='image_pull_policy',
|
||||||
|
values=['always', 'never', 'ifnotpresent']))
|
||||||
|
|
||||||
|
test_value = 'ALWAYS'
|
||||||
|
self.assertEqual('always', types.EnumType.validate(
|
||||||
|
test_value, name='image_pull_policy',
|
||||||
|
values=['always', 'never', 'ifnotpresent']))
|
||||||
|
|
||||||
|
test_value = None
|
||||||
|
self.assertEqual(None, types.EnumType.validate(
|
||||||
|
test_value, name='image_pull_policy',
|
||||||
|
values=['always', 'never', 'ifnotpresent']))
|
||||||
|
|
||||||
|
test_value = 'xyz'
|
||||||
|
self.assertRaises(exception.InvalidValue,
|
||||||
|
types.EnumType.validate,
|
||||||
|
test_value,
|
||||||
|
name='image_pull_policy',
|
||||||
|
values=['always', 'never', 'ifnotpresent'])
|
||||||
|
@ -226,8 +226,8 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
|
|
||||||
@patch('zun.compute.api.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
def test_get_all_has_status_reason(self, mock_container_list,
|
def test_get_all_has_status_reason_and_image_pull_policy(
|
||||||
mock_container_show):
|
self, mock_container_list, mock_container_show):
|
||||||
test_container = utils.get_test_container()
|
test_container = utils.get_test_container()
|
||||||
containers = [objects.Container(self.context, **test_container)]
|
containers = [objects.Container(self.context, **test_container)]
|
||||||
mock_container_list.return_value = containers
|
mock_container_list.return_value = containers
|
||||||
@ -240,6 +240,7 @@ class TestContainerController(api_base.FunctionalTest):
|
|||||||
self.assertEqual(test_container['uuid'],
|
self.assertEqual(test_container['uuid'],
|
||||||
actual_containers[0].get('uuid'))
|
actual_containers[0].get('uuid'))
|
||||||
self.assertIn('status_reason', actual_containers[0].keys())
|
self.assertIn('status_reason', actual_containers[0].keys())
|
||||||
|
self.assertIn('image_pull_policy', actual_containers[0].keys())
|
||||||
|
|
||||||
@patch('zun.compute.api.API.container_show')
|
@patch('zun.compute.api.API.container_show')
|
||||||
@patch('zun.objects.Container.list')
|
@patch('zun.objects.Container.list')
|
||||||
|
@ -54,3 +54,22 @@ class TestUtils(base.BaseTestCase):
|
|||||||
utils.parse_image_name('test:test'))
|
utils.parse_image_name('test:test'))
|
||||||
self.assertEqual(('test-test', 'test'),
|
self.assertEqual(('test-test', 'test'),
|
||||||
utils.parse_image_name('test-test:test'))
|
utils.parse_image_name('test-test:test'))
|
||||||
|
|
||||||
|
def test_get_image_pull_policy(self):
|
||||||
|
self.assertEqual('always',
|
||||||
|
utils.get_image_pull_policy('always',
|
||||||
|
'latest'))
|
||||||
|
self.assertEqual('always',
|
||||||
|
utils.get_image_pull_policy(None,
|
||||||
|
'latest'))
|
||||||
|
self.assertEqual('ifnotpresent',
|
||||||
|
utils.get_image_pull_policy(None,
|
||||||
|
'2.0'))
|
||||||
|
|
||||||
|
def test_should_pull_image(self):
|
||||||
|
self.assertFalse(utils.should_pull_image('never', True))
|
||||||
|
self.assertFalse(utils.should_pull_image('never', False))
|
||||||
|
self.assertTrue(utils.should_pull_image('always', True))
|
||||||
|
self.assertTrue(utils.should_pull_image('always', False))
|
||||||
|
self.assertTrue(utils.should_pull_image('ifnotpresent', False))
|
||||||
|
self.assertFalse(utils.should_pull_image('ifnotpresent', True))
|
||||||
|
@ -69,7 +69,7 @@ class TestManager(base.TestCase):
|
|||||||
mock_save.assert_called_with()
|
mock_save.assert_called_with()
|
||||||
mock_pull.assert_called_once_with(self.context,
|
mock_pull.assert_called_once_with(self.context,
|
||||||
container.image,
|
container.image,
|
||||||
'latest')
|
'latest', 'always')
|
||||||
mock_create.assert_called_once_with(container, 'fake_path')
|
mock_create.assert_called_once_with(container, 'fake_path')
|
||||||
|
|
||||||
@mock.patch.object(Container, 'save')
|
@mock.patch.object(Container, 'save')
|
||||||
|
@ -41,6 +41,7 @@ def get_test_container(**kw):
|
|||||||
'ports': kw.get('ports', [80, 443]),
|
'ports': kw.get('ports', [80, 443]),
|
||||||
'hostname': kw.get('hostname', 'testhost'),
|
'hostname': kw.get('hostname', 'testhost'),
|
||||||
'labels': kw.get('labels', {'key1': 'val1', 'key2': 'val2'}),
|
'labels': kw.get('labels', {'key1': 'val1', 'key2': 'val2'}),
|
||||||
|
'image_pull_policy': kw.get('image_pull_policy', 'always'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,8 +34,34 @@ class TestDriver(base.BaseTestCase):
|
|||||||
self.dfc_context_manager.__enter__.return_value = self.mock_docker
|
self.dfc_context_manager.__enter__.return_value = self.mock_docker
|
||||||
self.addCleanup(dfc_patcher.stop)
|
self.addCleanup(dfc_patcher.stop)
|
||||||
|
|
||||||
def test_pull_image_success(self):
|
@mock.patch.object(driver.DockerDriver,
|
||||||
ret = self.driver.pull_image(None, 'test_image', 'latest')
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_should_pull_no_image_not_present_locally(
|
||||||
|
self, mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = False
|
||||||
|
mock_search.return_value = None
|
||||||
|
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
||||||
|
None, 'nonexisting', 'tag', 'never')
|
||||||
|
|
||||||
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_should_pull_no_image_present_locally(
|
||||||
|
self, mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = False
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
|
self.assertEqual({'image': 'nginx', 'path': 'xyz'},
|
||||||
|
self.driver.pull_image(None, 'nonexisting',
|
||||||
|
'tag', 'never'))
|
||||||
|
|
||||||
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_success(self, mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
|
ret = self.driver.pull_image(None, 'test_image', 'latest', 'always')
|
||||||
self.assertEqual({'image': 'test_image', 'path': None}, ret)
|
self.assertEqual({'image': 'test_image', 'path': None}, ret)
|
||||||
self.mock_docker.pull.assert_called_once_with(
|
self.mock_docker.pull.assert_called_once_with(
|
||||||
'test_image',
|
'test_image',
|
||||||
@ -43,14 +69,20 @@ class TestDriver(base.BaseTestCase):
|
|||||||
stream=True)
|
stream=True)
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.parse_image_name')
|
@mock.patch('zun.common.utils.parse_image_name')
|
||||||
def test_pull_image_raises_API_error(self, mock_parse_image):
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_raises_API_error(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_parse_image):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_parse_image.return_value = ('repo', 'tag')
|
mock_parse_image.return_value = ('repo', 'tag')
|
||||||
with mock.patch.object(errors.APIError, '__str__',
|
with mock.patch.object(errors.APIError, '__str__',
|
||||||
return_value='404 Not Found') as mock_init:
|
return_value='404 Not Found') as mock_init:
|
||||||
self.mock_docker.pull = mock.Mock(
|
self.mock_docker.pull = mock.Mock(
|
||||||
side_effect=errors.APIError('Error', '', ''))
|
side_effect=errors.APIError('Error', '', ''))
|
||||||
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
||||||
None, 'repo', 'tag')
|
None, 'repo', 'tag', 'always')
|
||||||
self.mock_docker.pull.assert_called_once_with(
|
self.mock_docker.pull.assert_called_once_with(
|
||||||
'repo',
|
'repo',
|
||||||
tag='tag',
|
tag='tag',
|
||||||
@ -58,7 +90,13 @@ class TestDriver(base.BaseTestCase):
|
|||||||
self.assertEqual(1, mock_init.call_count)
|
self.assertEqual(1, mock_init.call_count)
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.parse_image_name')
|
@mock.patch('zun.common.utils.parse_image_name')
|
||||||
def test_pull_image_not_found(self, mock_parse_image):
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_not_found(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_parse_image):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_parse_image.return_value = ('repo', 'tag')
|
mock_parse_image.return_value = ('repo', 'tag')
|
||||||
pull_return_value = '{"errorDetail":{"message":'\
|
pull_return_value = '{"errorDetail":{"message":'\
|
||||||
'"Error: image library/repo not found"},'\
|
'"Error: image library/repo not found"},'\
|
||||||
@ -67,7 +105,7 @@ class TestDriver(base.BaseTestCase):
|
|||||||
with mock.patch.object(self.mock_docker, 'pull',
|
with mock.patch.object(self.mock_docker, 'pull',
|
||||||
return_value=[pull_return_value]) as mock_init:
|
return_value=[pull_return_value]) as mock_init:
|
||||||
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
||||||
None, 'repo', 'tag')
|
None, 'repo', 'tag', 'always')
|
||||||
self.mock_docker.pull.assert_called_once_with(
|
self.mock_docker.pull.assert_called_once_with(
|
||||||
'repo',
|
'repo',
|
||||||
tag='tag',
|
tag='tag',
|
||||||
@ -75,7 +113,13 @@ class TestDriver(base.BaseTestCase):
|
|||||||
self.assertEqual(1, mock_init.call_count)
|
self.assertEqual(1, mock_init.call_count)
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.parse_image_name')
|
@mock.patch('zun.common.utils.parse_image_name')
|
||||||
def test_pull_image_raises_docker_error(self, mock_parse_image):
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_raises_docker_error(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_parse_image):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_parse_image.return_value = ('repo', 'tag')
|
mock_parse_image.return_value = ('repo', 'tag')
|
||||||
pull_return_value = '{"errorDetail":{"message":'\
|
pull_return_value = '{"errorDetail":{"message":'\
|
||||||
'"Error: image library/repo not"},'\
|
'"Error: image library/repo not"},'\
|
||||||
@ -84,7 +128,7 @@ class TestDriver(base.BaseTestCase):
|
|||||||
with mock.patch.object(self.mock_docker, 'pull',
|
with mock.patch.object(self.mock_docker, 'pull',
|
||||||
return_value=[pull_return_value]) as mock_init:
|
return_value=[pull_return_value]) as mock_init:
|
||||||
self.assertRaises(exception.DockerError, self.driver.pull_image,
|
self.assertRaises(exception.DockerError, self.driver.pull_image,
|
||||||
None, 'repo', 'tag')
|
None, 'repo', 'tag', 'always')
|
||||||
self.mock_docker.pull.assert_called_once_with(
|
self.mock_docker.pull.assert_called_once_with(
|
||||||
'repo',
|
'repo',
|
||||||
tag='tag',
|
tag='tag',
|
||||||
@ -92,14 +136,20 @@ class TestDriver(base.BaseTestCase):
|
|||||||
self.assertEqual(1, mock_init.call_count)
|
self.assertEqual(1, mock_init.call_count)
|
||||||
|
|
||||||
@mock.patch('zun.common.utils.parse_image_name')
|
@mock.patch('zun.common.utils.parse_image_name')
|
||||||
def test_pull_image_exception(self, mock_parse_image):
|
@mock.patch.object(driver.DockerDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_exception(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_parse_image):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_parse_image.return_value = ('repo', 'tag')
|
mock_parse_image.return_value = ('repo', 'tag')
|
||||||
with mock.patch.object(errors.APIError, '__str__',
|
with mock.patch.object(errors.APIError, '__str__',
|
||||||
return_value='hit error') as mock_init:
|
return_value='hit error') as mock_init:
|
||||||
self.mock_docker.pull = mock.Mock(
|
self.mock_docker.pull = mock.Mock(
|
||||||
side_effect=errors.APIError('Error', '', ''))
|
side_effect=errors.APIError('Error', '', ''))
|
||||||
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
||||||
None, 'repo', 'tag')
|
None, 'repo', 'tag', 'always')
|
||||||
self.mock_docker.pull.assert_called_once_with(
|
self.mock_docker.pull.assert_called_once_with(
|
||||||
'repo',
|
'repo',
|
||||||
tag='tag',
|
tag='tag',
|
||||||
|
@ -36,28 +36,67 @@ class TestDriver(base.BaseTestCase):
|
|||||||
super(TestDriver, self).tearDown()
|
super(TestDriver, self).tearDown()
|
||||||
shutil.rmtree(self.test_dir)
|
shutil.rmtree(self.test_dir)
|
||||||
|
|
||||||
|
@mock.patch.object(driver.GlanceDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_should_pull_no_image_not_present_locally(
|
||||||
|
self, mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = False
|
||||||
|
mock_search.return_value = None
|
||||||
|
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
||||||
|
None, 'nonexisting', 'tag', 'never')
|
||||||
|
|
||||||
|
@mock.patch.object(driver.GlanceDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_should_pull_no_image_present_locally(
|
||||||
|
self, mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = False
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
|
self.assertEqual({'image': 'nginx', 'path': 'xyz'},
|
||||||
|
self.driver.pull_image(None, 'nonexisting',
|
||||||
|
'tag', 'never'))
|
||||||
|
|
||||||
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
||||||
def test_pull_image_failure(self, mock_glance):
|
@mock.patch.object(driver.GlanceDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_failure(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_glance):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_glance.side_effect = Exception
|
mock_glance.side_effect = Exception
|
||||||
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
self.assertRaises(exception.ZunException, self.driver.pull_image,
|
||||||
None, 'nonexisting', 'tag')
|
None, 'nonexisting', 'tag', 'always')
|
||||||
|
|
||||||
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
|
||||||
def test_pull_image_not_found(self, mock_glance):
|
|
||||||
with mock.patch('zun.image.glance.utils.find_image') as mock_find:
|
|
||||||
mock_find.side_effect = exception.ImageNotFound
|
|
||||||
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
|
||||||
None, 'nonexisting', 'tag')
|
|
||||||
|
|
||||||
|
@mock.patch.object(driver.GlanceDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
||||||
@mock.patch('zun.image.glance.utils.find_image')
|
@mock.patch('zun.image.glance.utils.find_image')
|
||||||
def test_pull_image_found(self, mock_find_image, mock_glance):
|
def test_pull_image(self, mock_find_image, mock_glance,
|
||||||
|
mock_should_pull_image, mock_search):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
mock_glance.images.data = mock.MagicMock(return_value='content')
|
mock_glance.images.data = mock.MagicMock(return_value='content')
|
||||||
image_meta = mock.MagicMock()
|
image_meta = mock.MagicMock()
|
||||||
image_meta.id = '1234'
|
image_meta.id = '1234'
|
||||||
mock_find_image.return_value = image_meta
|
mock_find_image.return_value = image_meta
|
||||||
CONF.set_override('images_directory', self.test_dir, group='glance')
|
CONF.set_override('images_directory', self.test_dir, group='glance')
|
||||||
out_path = os.path.join(self.test_dir, '1234' + '.tar')
|
out_path = os.path.join(self.test_dir, '1234' + '.tar')
|
||||||
ret = self.driver.pull_image(None, 'image', 'latest')
|
ret = self.driver.pull_image(None, 'image', 'latest', 'always')
|
||||||
self.assertEqual({'image': 'image', 'path': out_path}, ret)
|
self.assertEqual({'image': 'image', 'path': out_path}, ret)
|
||||||
self.assertTrue(os.path.isfile(ret['path']))
|
self.assertTrue(os.path.isfile(ret['path']))
|
||||||
|
|
||||||
|
@mock.patch('zun.image.glance.utils.create_glanceclient')
|
||||||
|
@mock.patch.object(driver.GlanceDriver,
|
||||||
|
'_search_image_on_host')
|
||||||
|
@mock.patch('zun.common.utils.should_pull_image')
|
||||||
|
def test_pull_image_not_found(self, mock_should_pull_image,
|
||||||
|
mock_search, mock_glance):
|
||||||
|
mock_should_pull_image.return_value = True
|
||||||
|
mock_search.return_value = {'image': 'nginx', 'path': 'xyz'}
|
||||||
|
with mock.patch('zun.image.glance.utils.find_image') as mock_find:
|
||||||
|
mock_find.side_effect = exception.ImageNotFound
|
||||||
|
self.assertRaises(exception.ImageNotFound, self.driver.pull_image,
|
||||||
|
None, 'nonexisting', 'tag', 'always')
|
||||||
|
Loading…
Reference in New Issue
Block a user