Add different upload cleanup behaviours

By default, images left over from upload operations are deleted from
the local docker, however there are cases where this is not desirable:
- when installing an undercloud, deleting the images then
  re-downloading from the undercloud repository wastes time
- when developing with a workflow where you do not want local images
  to be deleted ever (to save time, or preserve local image changes)

Change-Id: Id844aa2fa5ee20ad264b2fada75fa6cffdc1e307
Blueprint: container-prepare-workflow
This commit is contained in:
Steve Baker 2018-06-27 12:19:10 +12:00
parent 5191b65676
commit 15bf0f588c
4 changed files with 77 additions and 37 deletions

View File

@ -48,6 +48,12 @@ SECURE_REGISTRIES = (
'docker.io', 'docker.io',
) )
CLEANUP = (
CLEANUP_FULL, CLEANUP_PARTIAL, CLEANUP_NONE
) = (
'full', 'partial', 'none'
)
def get_undercloud_registry(): def get_undercloud_registry():
addr = 'localhost' addr = 'localhost'
@ -66,12 +72,13 @@ class ImageUploadManager(BaseImageManager):
""" """
def __init__(self, config_files=None, verbose=False, debug=False, def __init__(self, config_files=None, verbose=False, debug=False,
dry_run=False): dry_run=False, cleanup=CLEANUP_FULL):
if config_files is None: if config_files is None:
config_files = [] config_files = []
super(ImageUploadManager, self).__init__(config_files) super(ImageUploadManager, self).__init__(config_files)
self.uploaders = {} self.uploaders = {}
self.dry_run = dry_run self.dry_run = dry_run
self.cleanup = cleanup
def discover_image_tag(self, image, tag_from_label=None): def discover_image_tag(self, image, tag_from_label=None):
uploader = self.uploader('docker') uploader = self.uploader('docker')
@ -108,7 +115,8 @@ class ImageUploadManager(BaseImageManager):
self.uploader(uploader).add_upload_task( self.uploader(uploader).add_upload_task(
image_name, pull_source, push_destination, image_name, pull_source, push_destination,
append_tag, modify_role, modify_vars, self.dry_run) append_tag, modify_role, modify_vars, self.dry_run,
self.cleanup)
for uploader in self.uploaders.values(): for uploader in self.uploaders.values():
uploader.run_tasks() uploader.run_tasks()
@ -133,7 +141,8 @@ class ImageUploader(object):
@abc.abstractmethod @abc.abstractmethod
def add_upload_task(self, image_name, pull_source, push_destination, def add_upload_task(self, image_name, pull_source, push_destination,
append_tag, modify_role, modify_vars, dry_run): append_tag, modify_role, modify_vars, dry_run,
cleanup):
"""Add an upload task to be executed later""" """Add an upload task to be executed later"""
pass pass
@ -199,7 +208,7 @@ class DockerImageUploader(ImageUploader):
@staticmethod @staticmethod
def upload_image(image_name, pull_source, push_destination, def upload_image(image_name, pull_source, push_destination,
insecure_registries, append_tag, modify_role, insecure_registries, append_tag, modify_role,
modify_vars, dry_run): modify_vars, dry_run, cleanup):
LOG.info('imagename: %s' % image_name) LOG.info('imagename: %s' % image_name)
if ':' in image_name: if ':' in image_name:
image = image_name.rpartition(':')[0] image = image_name.rpartition(':')[0]
@ -254,7 +263,11 @@ class DockerImageUploader(ImageUploader):
DockerImageUploader._push(dockerc, target_image_no_tag, tag=target_tag) DockerImageUploader._push(dockerc, target_image_no_tag, tag=target_tag)
LOG.info('Completed upload for image %s' % image_name) LOG.info('Completed upload for image %s' % image_name)
return source_image, target_image if cleanup == CLEANUP_NONE:
return []
if cleanup == CLEANUP_PARTIAL:
return [source_image]
return [source_image, target_image]
@staticmethod @staticmethod
@tenacity.retry( # Retry up to 5 times with jittered exponential backoff @tenacity.retry( # Retry up to 5 times with jittered exponential backoff
@ -436,7 +449,8 @@ class DockerImageUploader(ImageUploader):
LOG.warning(e) LOG.warning(e)
def add_upload_task(self, image_name, pull_source, push_destination, def add_upload_task(self, image_name, pull_source, push_destination,
append_tag, modify_role, modify_vars, dry_run): append_tag, modify_role, modify_vars, dry_run,
cleanup):
# prime self.insecure_registries # prime self.insecure_registries
if pull_source: if pull_source:
self.is_insecure_registry(self._image_to_url(pull_source).netloc) self.is_insecure_registry(self._image_to_url(pull_source).netloc)
@ -445,7 +459,7 @@ class DockerImageUploader(ImageUploader):
self.is_insecure_registry(self._image_to_url(push_destination).netloc) self.is_insecure_registry(self._image_to_url(push_destination).netloc)
self.upload_tasks.append((image_name, pull_source, push_destination, self.upload_tasks.append((image_name, pull_source, push_destination,
self.insecure_registries, append_tag, self.insecure_registries, append_tag,
modify_role, modify_vars, dry_run)) modify_role, modify_vars, dry_run, cleanup))
def run_tasks(self): def run_tasks(self):
if not self.upload_tasks: if not self.upload_tasks:

View File

@ -98,7 +98,8 @@ def build_service_filter(environment, roles_data):
return containerized_services.intersection(enabled_services) return containerized_services.intersection(enabled_services)
def container_images_prepare_multi(environment, roles_data, dry_run=False): def container_images_prepare_multi(environment, roles_data, dry_run=False,
cleanup=image_uploader.CLEANUP_FULL):
"""Perform multiple container image prepares and merge result """Perform multiple container image prepares and merge result
Given the full heat environment and roles data, perform multiple image Given the full heat environment and roles data, perform multiple image
@ -160,6 +161,7 @@ def container_images_prepare_multi(environment, roles_data, dry_run=False):
[f.name], [f.name],
verbose=True, verbose=True,
dry_run=dry_run, dry_run=dry_run,
cleanup=cleanup
) )
uploader.upload() uploader.upload()
return env_params return env_params

View File

@ -162,14 +162,21 @@ class TestDockerImageUploader(base.TestCase):
push_destination = 'localhost:8787' push_destination = 'localhost:8787'
push_image = 'localhost:8787/tripleomaster/heat-docker-agents-centos' push_image = 'localhost:8787/tripleomaster/heat-docker-agents-centos'
self.uploader.upload_image(image + ':' + tag, self.assertEqual(
None, ['docker.io/tripleomaster/heat-docker-agents-centos:latest',
push_destination, 'localhost:8787/tripleomaster/heat-docker-agents-centos:latest'],
set(), self.uploader.upload_image(
None, image + ':' + tag,
None, None,
None, push_destination,
False) set(),
None,
None,
None,
False,
'full'
)
)
self.dockermock.assert_called_once_with( self.dockermock.assert_called_once_with(
base_url='unix://var/run/docker.sock', version='auto') base_url='unix://var/run/docker.sock', version='auto')
@ -198,7 +205,8 @@ class TestDockerImageUploader(base.TestCase):
None, None,
None, None,
None, None,
False) False,
'full')
self.dockermock.assert_called_once_with( self.dockermock.assert_called_once_with(
base_url='unix://var/run/docker.sock', version='auto') base_url='unix://var/run/docker.sock', version='auto')
@ -226,14 +234,20 @@ class TestDockerImageUploader(base.TestCase):
tag = 'latest' tag = 'latest'
push_destination = 'localhost:8787' push_destination = 'localhost:8787'
self.uploader.upload_image(image + ':' + tag, self.assertEqual(
None, [],
push_destination, self.uploader.upload_image(
set(), image + ':' + tag,
None, None,
None, push_destination,
None, set(),
False) None,
None,
None,
False,
'full'
)
)
# both digests are the same, no pull/push # both digests are the same, no pull/push
self.dockermock.assert_not_called() self.dockermock.assert_not_called()
@ -275,14 +289,21 @@ class TestDockerImageUploader(base.TestCase):
'hosts': 'localhost' 'hosts': 'localhost'
}] }]
self.uploader.upload_image(image + ':' + tag, # test response for a partial cleanup
None, self.assertEqual(
push_destination, ['docker.io/tripleomaster/heat-docker-agents-centos:latest'],
set(), self.uploader.upload_image(
append_tag, image + ':' + tag,
'add-foo-plugin', None,
{'foo_version': '1.0.1'}, push_destination,
False) set(),
append_tag,
'add-foo-plugin',
{'foo_version': '1.0.1'},
False,
'partial'
)
)
self.dockermock.assert_called_once_with( self.dockermock.assert_called_once_with(
base_url='unix://var/run/docker.sock', version='auto') base_url='unix://var/run/docker.sock', version='auto')
@ -322,7 +343,7 @@ class TestDockerImageUploader(base.TestCase):
processutils.ProcessExecutionError, processutils.ProcessExecutionError,
self.uploader.upload_image, self.uploader.upload_image,
image + ':' + tag, None, push_destination, set(), append_tag, image + ':' + tag, None, push_destination, set(), append_tag,
'add-foo-plugin', {'foo_version': '1.0.1'}, False 'add-foo-plugin', {'foo_version': '1.0.1'}, False, 'full'
) )
self.dockermock.assert_called_once_with( self.dockermock.assert_called_once_with(
@ -353,7 +374,8 @@ class TestDockerImageUploader(base.TestCase):
append_tag, append_tag,
'add-foo-plugin', 'add-foo-plugin',
{'foo_version': '1.0.1'}, {'foo_version': '1.0.1'},
True True,
'full'
) )
self.dockermock.assert_not_called() self.dockermock.assert_not_called()
@ -381,7 +403,8 @@ class TestDockerImageUploader(base.TestCase):
append_tag, append_tag,
'add-foo-plugin', 'add-foo-plugin',
{'foo_version': '1.0.1'}, {'foo_version': '1.0.1'},
False False,
'full'
) )
self.dockermock.assert_not_called() self.dockermock.assert_not_called()

View File

@ -911,7 +911,8 @@ class TestPrepare(base.TestCase):
) )
]) ])
mock_im.assert_called_once_with(mock.ANY, dry_run=True, verbose=True) mock_im.assert_called_once_with(mock.ANY, dry_run=True, verbose=True,
cleanup='full')
self.assertEqual( self.assertEqual(
{ {