mirror of
https://opendev.org/openstack/kolla.git
synced 2026-01-16 23:10:33 +00:00
Add a way to add other container engines
Add engine adapter module to allow additon of other container engines, create one common EngineClient that would be called in all other modules. Based on patch by Konstantin Yarovoy <konstantin.yarovoy@tietoevry.com> Change-Id: Ice6467086bd292af086322afc3fc4e869d89eefa
This commit is contained in:
parent
e275875c25
commit
a2854da1b3
7 changed files with 150 additions and 67 deletions
|
|
@ -162,7 +162,8 @@ _CLI_OPTS = [
|
|||
cfg.BoolOpt('skip-parents', default=False,
|
||||
help='Do not rebuild parents of matched images'),
|
||||
cfg.BoolOpt('skip-existing', default=False,
|
||||
help='Do not rebuild images present in the docker cache'),
|
||||
help='Do not rebuild images present in the container engine '
|
||||
'cache'),
|
||||
cfg.DictOpt('build-args',
|
||||
help='Set docker build time variables'),
|
||||
cfg.BoolOpt('keep', default=False,
|
||||
|
|
@ -176,7 +177,7 @@ _CLI_OPTS = [
|
|||
cfg.StrOpt('network_mode', default='host',
|
||||
help='The network mode for Docker build. Example: host'),
|
||||
cfg.BoolOpt('cache', default=True,
|
||||
help='Use the Docker cache when building'),
|
||||
help='Use the container engine cache when building'),
|
||||
cfg.MultiOpt('profile', types.String(), short='p',
|
||||
help=('Build a pre-defined set of images, see [profiles]'
|
||||
' section in config. The default profiles are:'
|
||||
|
|
@ -194,14 +195,14 @@ _CLI_OPTS = [
|
|||
help=('Build only images matching regex and its'
|
||||
' dependencies')),
|
||||
cfg.StrOpt('registry',
|
||||
help=('The docker registry host. The default registry host'
|
||||
' is Docker Hub')),
|
||||
help=('The container image registry host. The default registry'
|
||||
' host is Docker Hub')),
|
||||
cfg.StrOpt('save-dependency',
|
||||
help=('Path to the file to store the docker image'
|
||||
' dependency in Graphviz dot format')),
|
||||
cfg.StrOpt('format', short='f', default='json',
|
||||
choices=['json', 'none'],
|
||||
help='Format to write the final results in'),
|
||||
help='Format to write the final results in.'),
|
||||
cfg.StrOpt('tarballs-base', default=TARBALLS_BASE,
|
||||
help='Base url to OpenStack tarballs'),
|
||||
# NOTE(hrw): deprecate argument in Zed, remove in A-cycle
|
||||
|
|
@ -214,7 +215,7 @@ _CLI_OPTS = [
|
|||
' (Note: setting to one will allow real time'
|
||||
' logging)')),
|
||||
cfg.StrOpt('tag', default=version.cached_version_string(),
|
||||
help='The Docker tag'),
|
||||
help='The container image tag'),
|
||||
cfg.BoolOpt('template-only', default=False,
|
||||
help="Don't build images. Generate Dockerfile only"),
|
||||
cfg.IntOpt('timeout', default=120,
|
||||
|
|
@ -255,6 +256,8 @@ _CLI_OPTS = [
|
|||
help='Prefix prepended to image names'),
|
||||
cfg.StrOpt('repos-yaml', default='',
|
||||
help='Path to alternative repos.yaml file'),
|
||||
cfg.StrOpt('engine', default='docker', choices=['docker'],
|
||||
help='Container engine to build images on.')
|
||||
]
|
||||
|
||||
_BASE_OPTS = [
|
||||
|
|
|
|||
0
kolla/engine_adapter/__init__.py
Normal file
0
kolla/engine_adapter/__init__.py
Normal file
57
kolla/engine_adapter/engine.py
Normal file
57
kolla/engine_adapter/engine.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# 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.
|
||||
|
||||
from distutils.version import StrictVersion
|
||||
from enum import Enum
|
||||
from kolla.image.utils import LOG
|
||||
|
||||
try:
|
||||
import docker
|
||||
except (ImportError):
|
||||
LOG.debug("Docker python library was not found")
|
||||
|
||||
|
||||
class Engine(Enum):
|
||||
|
||||
DOCKER = "docker"
|
||||
|
||||
|
||||
class UnsupportedEngineError(ValueError):
|
||||
|
||||
def __init__(self, engine_name):
|
||||
super().__init__()
|
||||
self.engine_name = engine_name
|
||||
|
||||
def __str__(self):
|
||||
return f'Unsupported engine name given: "{self.engine_name}"'
|
||||
|
||||
|
||||
def getEngineException(conf):
|
||||
if conf.engine == Engine.DOCKER.value:
|
||||
return (docker.errors.DockerException)
|
||||
else:
|
||||
raise UnsupportedEngineError(conf.engine)
|
||||
|
||||
|
||||
def getEngineClient(conf):
|
||||
if conf.engine == Engine.DOCKER.value:
|
||||
kwargs_env = docker.utils.kwargs_from_env()
|
||||
return docker.APIClient(version='auto', **kwargs_env)
|
||||
else:
|
||||
raise UnsupportedEngineError(conf.engine)
|
||||
|
||||
|
||||
def getEngineVersion(conf):
|
||||
if conf.engine == Engine.DOCKER.value:
|
||||
return StrictVersion(docker.version)
|
||||
else:
|
||||
raise UnsupportedEngineError(conf.engine)
|
||||
|
|
@ -21,6 +21,7 @@ import time
|
|||
|
||||
from kolla.common import config as common_config
|
||||
from kolla.common import utils
|
||||
from kolla.engine_adapter import engine
|
||||
from kolla.image.kolla_worker import KollaWorker
|
||||
from kolla.image.utils import LOG
|
||||
from kolla.image.utils import Status
|
||||
|
|
@ -104,10 +105,28 @@ def run_build():
|
|||
if conf.debug:
|
||||
LOG.setLevel(logging.DEBUG)
|
||||
|
||||
if conf.squash:
|
||||
squash_version = utils.get_docker_squash_version()
|
||||
LOG.info('Image squash is enabled and "docker-squash" version is %s',
|
||||
squash_version)
|
||||
if conf.engine not in (engine.Engine.DOCKER.value,):
|
||||
LOG.error(f'Unsupported engine name "{conf.engine}", exiting.')
|
||||
sys.exit(1)
|
||||
LOG.info(f'Using engine: {conf.engine}')
|
||||
|
||||
if conf.engine == engine.Engine.DOCKER.value:
|
||||
try:
|
||||
import docker
|
||||
docker.version
|
||||
except ImportError:
|
||||
LOG.error("Error, you have set Docker as container engine, "
|
||||
"but the Python library is not found."
|
||||
"Try running 'pip install docker'")
|
||||
sys.exit(1)
|
||||
except AttributeError:
|
||||
LOG.error("Error, Docker Python library is too old, "
|
||||
"Try running 'pip install docker --upgrade'")
|
||||
|
||||
if conf.squash:
|
||||
squash_version = utils.get_docker_squash_version()
|
||||
LOG.info('Image squash is enabled and "docker-squash" version '
|
||||
'is %s', squash_version)
|
||||
|
||||
kolla = KollaWorker(conf)
|
||||
kolla.setup_working_dir()
|
||||
|
|
@ -133,7 +152,7 @@ def run_build():
|
|||
|
||||
if conf.save_dependency:
|
||||
kolla.save_dependency(conf.save_dependency)
|
||||
LOG.info('Docker images dependency are saved in %s',
|
||||
LOG.info('Container images dependency are saved in %s',
|
||||
conf.save_dependency)
|
||||
return
|
||||
if conf.list_images:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import docker
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
|
|
@ -24,6 +23,7 @@ import time
|
|||
import jinja2
|
||||
from kolla.common import config as common_config
|
||||
from kolla.common import utils
|
||||
from kolla.engine_adapter import engine
|
||||
from kolla import exception
|
||||
from kolla.image.tasks import BuildTask
|
||||
from kolla.image.unbuildable import UNBUILDABLE_IMAGES
|
||||
|
|
@ -42,7 +42,7 @@ PROJECT_ROOT = os.path.abspath(os.path.join(
|
|||
class Image(object):
|
||||
def __init__(self, name, canonical_name, path, parent_name='',
|
||||
status=Status.UNPROCESSED, parent=None,
|
||||
source=None, logger=None, docker_client=None):
|
||||
source=None, logger=None, engine_client=None):
|
||||
self.name = name
|
||||
self.canonical_name = canonical_name
|
||||
self.path = path
|
||||
|
|
@ -56,7 +56,7 @@ class Image(object):
|
|||
self.children = []
|
||||
self.plugins = []
|
||||
self.additions = []
|
||||
self.dc = docker_client
|
||||
self.engine_client = engine_client
|
||||
|
||||
def copy(self):
|
||||
c = Image(self.name, self.canonical_name, self.path,
|
||||
|
|
@ -72,8 +72,9 @@ class Image(object):
|
|||
c.additions = list(self.additions)
|
||||
return c
|
||||
|
||||
def in_docker_cache(self):
|
||||
return len(self.dc.images(name=self.canonical_name, quiet=True)) == 1
|
||||
def in_engine_cache(self):
|
||||
return len(self.engine_client.images(name=self.canonical_name,
|
||||
quiet=True)) == 1
|
||||
|
||||
def __repr__(self):
|
||||
return ("Image(%s, %s, %s, parent_name=%s,"
|
||||
|
|
@ -148,16 +149,16 @@ class KollaWorker(object):
|
|||
self.maintainer = conf.maintainer
|
||||
self.distro_python_version = conf.distro_python_version
|
||||
|
||||
docker_kwargs = docker.utils.kwargs_from_env()
|
||||
try:
|
||||
self.dc = docker.APIClient(version='auto', **docker_kwargs)
|
||||
except docker.errors.DockerException as e:
|
||||
self.dc = None
|
||||
self.engine_client = engine.getEngineClient(self.conf)
|
||||
except engine.getEngineException(self.conf) as e:
|
||||
self.engine_client = None
|
||||
if not (conf.template_only or
|
||||
conf.save_dependency or
|
||||
conf.list_images or
|
||||
conf.list_dependencies):
|
||||
LOG.error("Unable to connect to Docker, exiting")
|
||||
LOG.error("Unable to connect to container engine daemon, "
|
||||
"exiting")
|
||||
LOG.info("Exception caught: {0}".format(e))
|
||||
sys.exit(1)
|
||||
|
||||
|
|
@ -179,18 +180,18 @@ class KollaWorker(object):
|
|||
# this is the correct path
|
||||
# TODO(SamYaple): Improve this to make this safer
|
||||
if os.path.exists(os.path.join(image_path, 'base')):
|
||||
LOG.info('Found the docker image folder at %s', image_path)
|
||||
LOG.info('Found the container image folder at %s', image_path)
|
||||
return image_path
|
||||
else:
|
||||
raise exception.KollaDirNotFoundException('Image dir can not '
|
||||
'be found')
|
||||
|
||||
def build_rpm_setup(self, rpm_setup_config):
|
||||
"""Generates a list of docker commands based on provided configuration.
|
||||
"""Generates a list of engine commands based on provided configuration
|
||||
|
||||
:param rpm_setup_config: A list of .rpm or .repo paths or URLs
|
||||
(can be empty)
|
||||
:return: A list of docker commands
|
||||
:return: A list of engine commands
|
||||
"""
|
||||
rpm_setup = list()
|
||||
|
||||
|
|
@ -470,7 +471,7 @@ class KollaWorker(object):
|
|||
if image.status != Status.MATCHED:
|
||||
continue
|
||||
# Skip image if --skip-existing was given and image exists.
|
||||
if (self.conf.skip_existing and image.in_docker_cache()):
|
||||
if (self.conf.skip_existing and image.in_engine_cache()):
|
||||
LOG.debug('Skipping existing image %s', image.name)
|
||||
image.status = Status.SKIPPED
|
||||
# Skip image if --skip-parents was given and image has children.
|
||||
|
|
@ -638,7 +639,7 @@ class KollaWorker(object):
|
|||
image = Image(image_name, canonical_name, path,
|
||||
parent_name=parent_name,
|
||||
logger=utils.make_a_logger(self.conf, image_name),
|
||||
docker_client=self.dc)
|
||||
engine_client=self.engine_client)
|
||||
|
||||
# NOTE(jeffrey4l): register the opts if the section didn't
|
||||
# register in the kolla/common/config.py file
|
||||
|
|
@ -683,7 +684,7 @@ class KollaWorker(object):
|
|||
except ImportError:
|
||||
LOG.error('"graphviz" is required for save dependency')
|
||||
raise
|
||||
dot = graphviz.Digraph(comment='Docker Images Dependency')
|
||||
dot = graphviz.Digraph(comment='Container Images Dependency')
|
||||
dot.body.extend(['rankdir=LR'])
|
||||
for image in self.images:
|
||||
if image.status not in [Status.MATCHED]:
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import docker
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
|
|
@ -23,6 +22,7 @@ from requests import exceptions as requests_exc
|
|||
|
||||
from kolla.common import task # noqa
|
||||
from kolla.common import utils # noqa
|
||||
from kolla.engine_adapter import engine
|
||||
from kolla.image.utils import Status
|
||||
from kolla.image.utils import STATUS_ERRORS
|
||||
|
||||
|
|
@ -31,21 +31,18 @@ class ArchivingError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class DockerTask(task.Task):
|
||||
|
||||
docker_kwargs = docker.utils.kwargs_from_env()
|
||||
|
||||
def __init__(self):
|
||||
super(DockerTask, self).__init__()
|
||||
self._dc = None
|
||||
class EngineTask(task.Task):
|
||||
def __init__(self, conf):
|
||||
super(EngineTask, self).__init__()
|
||||
self._ec = None
|
||||
self.conf = conf
|
||||
|
||||
@property
|
||||
def dc(self):
|
||||
if self._dc is not None:
|
||||
return self._dc
|
||||
docker_kwargs = self.docker_kwargs.copy()
|
||||
self._dc = docker.APIClient(version='auto', **docker_kwargs)
|
||||
return self._dc
|
||||
def engine_client(self):
|
||||
if self._ec is not None:
|
||||
return self._ec
|
||||
self._ec = engine.getEngineClient(self.conf)
|
||||
return self._ec
|
||||
|
||||
|
||||
class PushIntoQueueTask(task.Task):
|
||||
|
|
@ -70,11 +67,11 @@ class PushError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class PushTask(DockerTask):
|
||||
"""Task that pushes an image to a docker repository."""
|
||||
class PushTask(EngineTask):
|
||||
"""Task that pushes an image to a container image repository."""
|
||||
|
||||
def __init__(self, conf, image):
|
||||
super(PushTask, self).__init__()
|
||||
super(PushTask, self).__init__(conf)
|
||||
self.conf = conf
|
||||
self.image = image
|
||||
self.logger = image.logger
|
||||
|
|
@ -89,9 +86,10 @@ class PushTask(DockerTask):
|
|||
try:
|
||||
self.push_image(image)
|
||||
except requests_exc.ConnectionError:
|
||||
self.logger.exception('Make sure Docker is running and that you'
|
||||
' have the correct privileges to run Docker'
|
||||
' (root)')
|
||||
self.logger.exception('Make sure container engine daemon is '
|
||||
'running and that you have the correct '
|
||||
'privileges to run the '
|
||||
'container engine (root)')
|
||||
image.status = Status.CONNECTION_ERROR
|
||||
except PushError as exception:
|
||||
self.logger.error(exception)
|
||||
|
|
@ -110,7 +108,8 @@ class PushTask(DockerTask):
|
|||
def push_image(self, image):
|
||||
kwargs = dict(stream=True, decode=True)
|
||||
|
||||
for response in self.dc.push(image.canonical_name, **kwargs):
|
||||
for response in self.engine_client.push(
|
||||
image.canonical_name, **kwargs):
|
||||
if 'stream' in response:
|
||||
self.logger.info(response['stream'])
|
||||
elif 'errorDetail' in response:
|
||||
|
|
@ -120,11 +119,11 @@ class PushTask(DockerTask):
|
|||
image.status = Status.BUILT
|
||||
|
||||
|
||||
class BuildTask(DockerTask):
|
||||
class BuildTask(EngineTask):
|
||||
"""Task that builds out an image."""
|
||||
|
||||
def __init__(self, conf, image, push_queue):
|
||||
super(BuildTask, self).__init__()
|
||||
super(BuildTask, self).__init__(conf)
|
||||
self.conf = conf
|
||||
self.image = image
|
||||
self.push_queue = push_queue
|
||||
|
|
@ -145,8 +144,9 @@ class BuildTask(DockerTask):
|
|||
followups = []
|
||||
if self.conf.push and self.success:
|
||||
followups.extend([
|
||||
# If we are supposed to push the image into a docker
|
||||
# repository, then make sure we do that...
|
||||
# If we are supposed to push the image into a
|
||||
# container image repository,
|
||||
# then make sure we do that...
|
||||
PushIntoQueueTask(
|
||||
PushTask(self.conf, self.image),
|
||||
self.push_queue),
|
||||
|
|
@ -348,15 +348,16 @@ class BuildTask(DockerTask):
|
|||
|
||||
buildargs = self.update_buildargs()
|
||||
try:
|
||||
for stream in self.dc.build(path=image.path,
|
||||
tag=image.canonical_name,
|
||||
nocache=not self.conf.cache,
|
||||
rm=True,
|
||||
decode=True,
|
||||
network_mode=self.conf.network_mode,
|
||||
pull=pull,
|
||||
forcerm=self.forcerm,
|
||||
buildargs=buildargs):
|
||||
for stream in \
|
||||
self.engine_client.build(path=image.path,
|
||||
tag=image.canonical_name,
|
||||
nocache=not self.conf.cache,
|
||||
rm=True,
|
||||
decode=True,
|
||||
network_mode=self.conf.network_mode,
|
||||
pull=pull,
|
||||
forcerm=self.forcerm,
|
||||
buildargs=buildargs):
|
||||
if 'stream' in stream:
|
||||
for line in stream['stream'].split('\n'):
|
||||
if line:
|
||||
|
|
@ -369,11 +370,13 @@ class BuildTask(DockerTask):
|
|||
self.logger.error('%s', line)
|
||||
return
|
||||
|
||||
if image.status != Status.ERROR and self.conf.squash:
|
||||
if image.status != Status.ERROR and self.conf.squash and \
|
||||
self.conf.engine == engine.Engine.DOCKER.value:
|
||||
self.squash()
|
||||
except docker.errors.DockerException:
|
||||
except engine.getEngineException(self.conf):
|
||||
image.status = Status.ERROR
|
||||
self.logger.exception('Unknown docker error when building')
|
||||
self.logger.exception('Unknown container engine '
|
||||
'error when building')
|
||||
except Exception:
|
||||
image.status = Status.ERROR
|
||||
self.logger.exception('Unknown error when building')
|
||||
|
|
@ -385,9 +388,9 @@ class BuildTask(DockerTask):
|
|||
|
||||
def squash(self):
|
||||
image_tag = self.image.canonical_name
|
||||
image_id = self.dc.inspect_image(image_tag)['Id']
|
||||
image_id = self.engine_client.inspect_image(image_tag)['Id']
|
||||
|
||||
parent_history = self.dc.history(self.image.parent_name)
|
||||
parent_history = self.engine_client.history(self.image.parent_name)
|
||||
parent_last_layer = parent_history[0]['Id']
|
||||
self.logger.info('Parent lastest layer is: %s' % parent_last_layer)
|
||||
|
||||
|
|
|
|||
|
|
@ -493,7 +493,7 @@ class KollaWorkerTest(base.TestCase):
|
|||
self.assertEqual(utils.Status.SKIPPED, kolla.images[2].parent.status)
|
||||
self.assertEqual(utils.Status.SKIPPED, kolla.images[1].parent.status)
|
||||
|
||||
@mock.patch.object(Image, 'in_docker_cache')
|
||||
@mock.patch.object(Image, 'in_engine_cache')
|
||||
def test_skip_existing(self, mock_in_cache):
|
||||
mock_in_cache.side_effect = [True, False]
|
||||
self.conf.set_override('skip_existing', True)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue