Make overcloud_containers.yaml template driven

Currently container based CI jobs are implementing their own logic to
transform overcloud_containers.yaml to a file that reflects the
container images and registry required to run their job [1] unless the
job requires the exact images which the static
overcloud_containers.yaml provides[2].

This change adds a template overcloud_containers.yaml.j2 which will
exist in parallel to overcloud_containers.yaml until all downstream
users of overcloud_containers.yaml switch to a templated approach.

This change is the first step to write commands which allow
overcloud_containers.yaml to be built for a specific enviroment,
including version based tags and reducing the image list to those
needed by the deployment.

The test test_container_images_yaml_in_sync will ensure that any
changes made to overcloud_containers.yaml won't pass until they're
made to overcloud_containers.yaml.j2 as well. This will keep them in
sync until overcloud_containers.yaml is removed.

[1] https://github.com/rdo-infra/ansible-role-rdo-kolla-build/blob/master/tasks/main.yml#L39-L75
[2] https://github.com/openstack/tripleo-quickstart-extras/blob/master/roles/overcloud-prep-containers/templates/overcloud-prep-containers.sh.j2#L18

Change-Id: I4fe18565362f9f308dd9957aeb9bc0f7498590a6
Partial-Bug: #1696598
This commit is contained in:
Steve Baker 2017-06-07 09:14:52 +12:00
parent ad528e2ee6
commit dc0b057f6e
4 changed files with 237 additions and 21 deletions

View File

@ -0,0 +1,80 @@
{% set namespace=namespace or "tripleoupstream" %}
{% set name_prefix=name_prefix or "centos-binary-" %}
{% set name_suffix=name_suffix or "" %}
{% set tag=tag or "latest" %}
container_images_template:
- imagename: "{{namespace}}/{{name_prefix}}aodh-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}aodh-evaluator{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}aodh-listener{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}aodh-notifier{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ceilometer-central{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ceilometer-compute{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ceilometer-notification{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ceilometer-ipmi{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}cinder-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}cinder-backup{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}cinder-scheduler{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}cinder-volume{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}collectd{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}congress-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ec2-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}etcd{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}glance-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}gnocchi-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}gnocchi-metricd{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}gnocchi-statsd{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}haproxy{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}heat-api-cfn{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}heat-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}heat-engine{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}horizon{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ironic-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ironic-conductor{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}ironic-pxe{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}iscsid{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}keystone{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}manila-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}manila-base{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}manila-scheduler{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mariadb{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}memcached{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mistral-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mistral-engine{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mistral-executor{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mistral-event-engine{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}mongodb{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}multipathd{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-dhcp-agent{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-l3-agent{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-metadata-agent{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-openvswitch-agent{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-sriov-agent{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}neutron-server{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-base{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-compute-ironic{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-compute{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-conductor{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-consoleauth{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-libvirt{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-novncproxy{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-placement-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-scheduler{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}octavia-base{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}octavia-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}octavia-health-manager{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}octavia-housekeeping{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}octavia-worker{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}panko-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}rabbitmq{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}redis{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}sahara-api{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}sahara-engine{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}sensu-client{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}swift-account{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}swift-container{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}swift-object{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}swift-proxy-server{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}tacker{{name_suffix}}:{{tag}}"
- imagename: "{{namespace}}/{{name_prefix}}zaqar{{name_suffix}}:{{tag}}"
- imagename: "ceph/daemon:tag-build-master-jewel-centos-7"

View File

@ -12,6 +12,7 @@
# 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 collections
import json import json
import os import os
import yaml import yaml
@ -25,9 +26,11 @@ class BaseImageManager(object):
logger = log.getLogger(__name__ + '.BaseImageManager') logger = log.getLogger(__name__ + '.BaseImageManager')
APPEND_ATTRIBUTES = ['elements', 'options', 'packages'] APPEND_ATTRIBUTES = ['elements', 'options', 'packages']
CONFIG_SECTIONS = ( CONFIG_SECTIONS = (
DISK_IMAGES, UPLOADS, CONTAINER_IMAGES DISK_IMAGES, UPLOADS, CONTAINER_IMAGES,
CONTAINER_IMAGES_TEMPLATE
) = ( ) = (
'disk_images', 'uploads', 'container_images' 'disk_images', 'uploads', 'container_images',
'container_images_template'
) )
def __init__(self, config_files, images=None): def __init__(self, config_files, images=None):
@ -45,7 +48,7 @@ class BaseImageManager(object):
existing_image[attribute_name] = attribute existing_image[attribute_name] = attribute
def load_config_files(self, section): def load_config_files(self, section):
config_data = {} config_data = collections.OrderedDict()
for config_file in self.config_files: for config_file in self.config_files:
if os.path.isfile(config_file): if os.path.isfile(config_file):
with open(config_file) as cf: with open(config_file) as cf:

View File

@ -14,19 +14,16 @@
# #
import jinja2
import logging import logging
import os import os
import re import re
import subprocess import subprocess
import sys import sys
import yaml
from tripleo_common.image import base from tripleo_common.image import base
if sys.version_info[0] < 3:
import codecs
_open = open
open = codecs.open
class KollaImageBuilder(base.BaseImageManager): class KollaImageBuilder(base.BaseImageManager):
"""Build images using kolla-build""" """Build images using kolla-build"""
@ -53,6 +50,44 @@ class KollaImageBuilder(base.BaseImageManager):
# what results should be acceptable as a regex to build one image # what results should be acceptable as a regex to build one image
return imagename return imagename
def container_images_from_template(self, filter=None, **kwargs):
'''Build container_images data from container_images_template.
Any supplied keyword arguments are used for the substitution mapping to
transform the data in the config file container_images_template
section.
The resulting data resembles a config file which contains a valid
populated container_images section.
If a function is passed to the filter argument, this will be used to
modify the entry after substitution. If the filter function returns
None then the entry will not be added to the resulting list.
Defaults are applied so that when no arguments are provided the
resulting entries have the form:
- imagename: tripleoupstream/centos-binary-<name>:latest
'''
mapping = dict(kwargs)
result = []
if len(self.config_files) != 1:
raise ValueError('A single config file must be specified')
config_file = self.config_files[0]
with open(config_file) as cf:
template = jinja2.Template(cf.read())
rendered = template.render(mapping)
rendered_dict = yaml.safe_load(rendered)
for i in rendered_dict[self.CONTAINER_IMAGES_TEMPLATE]:
entry = dict(i)
if filter:
entry = filter(entry)
if entry is not None:
result.append(entry)
return result
def build_images(self, kolla_config_files=None): def build_images(self, kolla_config_files=None):
cmd = ['kolla-build'] cmd = ['kolla-build']

View File

@ -18,24 +18,45 @@ import mock
import os import os
import six import six
import subprocess import subprocess
import sys
import tempfile
import yaml
from tripleo_common.image import kolla_builder as kb from tripleo_common.image import kolla_builder as kb
from tripleo_common.tests import base from tripleo_common.tests import base
filedata = six.u( filedata = six.u("""container_images:
"""container_images: - imagename: tripleoupstream/heat-docker-agents-centos:latest
- imagename: tripleoupstream/heat-docker-agents-centos:latest push_destination: localhost:8787
push_destination: localhost:8787 - imagename: tripleoupstream/centos-binary-nova-compute:liberty
- imagename: tripleoupstream/centos-binary-nova-compute:liberty uploader: docker
uploader: docker pull_source: docker.io
pull_source: docker.io push_destination: localhost:8787
push_destination: localhost:8787 - imagename: tripleoupstream/centos-binary-nova-libvirt:liberty
- imagename: tripleoupstream/centos-binary-nova-libvirt:liberty uploader: docker
uploader: docker pull_source: docker.io
pull_source: docker.io - imagename: tripleoupstream/image-with-missing-tag
- imagename: tripleoupstream/image-with-missing-tag push_destination: localhost:8787
push_destination: localhost:8787 """)
template_filedata = six.u("""
{% set namespace=namespace or "tripleoupstream" %}
{% set name_prefix=name_prefix or "centos-binary-" %}
{% set name_suffix=name_suffix or "" %}
{% set tag=tag or "latest" %}
container_images_template:
- imagename: "{{namespace}}/heat-docker-agents-centos:latest"
push_destination: "{{push_destination}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-compute{{name_suffix}}:{{tag}}"
uploader: "docker"
pull_source: "{{pull_source}}"
push_destination: "{{push_destination}}"
- imagename: "{{namespace}}/{{name_prefix}}nova-libvirt{{name_suffix}}:{{tag}}"
uploader: "docker"
pull_source: "{{pull_source}}"
- imagename: "{{namespace}}/image-with-missing-tag"
push_destination: "{{push_destination}}"
""") """)
@ -108,3 +129,80 @@ class TestKollaImageBuilder(base.TestCase):
mock_popen.assert_called_once_with([ mock_popen.assert_called_once_with([
'kolla-build', 'kolla-build',
], env=env) ], env=env)
class TestKollaImageBuilderTemplate(base.TestCase):
def setUp(self):
super(TestKollaImageBuilderTemplate, self).setUp()
with tempfile.NamedTemporaryFile(delete=False) as imagefile:
self.addCleanup(os.remove, imagefile.name)
self.filelist = [imagefile.name]
with open(imagefile.name, 'w') as f:
f.write(template_filedata)
def test_container_images_from_template(self):
builder = kb.KollaImageBuilder(self.filelist)
result = builder.container_images_from_template(
pull_source='docker.io',
push_destination='localhost:8787',
tag='liberty'
)
# template substitution on the container_images_template section should
# be identical to the container_images section
container_images = yaml.safe_load(filedata)['container_images']
self.assertEqual(container_images, result)
def test_container_images_from_template_filter(self):
builder = kb.KollaImageBuilder(self.filelist)
def filter(entry):
# do not want heat-agents image
if 'heat-docker-agents' in entry.get('imagename'):
return
# set source and destination on all entries
entry['pull_source'] = 'docker.io'
entry['push_destination'] = 'localhost:8787'
return entry
result = builder.container_images_from_template(
filter=filter,
tag='liberty'
)
container_images = [{
'imagename': 'tripleoupstream/centos-binary-nova-compute:liberty',
'pull_source': 'docker.io',
'push_destination': 'localhost:8787',
'uploader': 'docker'
}, {
'imagename': 'tripleoupstream/centos-binary-nova-libvirt:liberty',
'pull_source': 'docker.io',
'push_destination': 'localhost:8787',
'uploader': 'docker'
}, {
'imagename': 'tripleoupstream/image-with-missing-tag',
'pull_source': 'docker.io',
'push_destination': 'localhost:8787'
}]
self.assertEqual(container_images, result)
def test_container_images_yaml_in_sync(self):
'''Confirm overcloud_containers.tpl.yaml equals overcloud_containers.yaml
TODO(sbaker) remove when overcloud_containers.yaml is deleted
'''
mod_dir = os.path.dirname(sys.modules[__name__].__file__)
project_dir = os.path.abspath(os.path.join(mod_dir, '../../../'))
files_dir = os.path.join(project_dir, 'container-images')
oc_tmpl_file = os.path.join(files_dir, 'overcloud_containers.yaml.j2')
tmpl_builder = kb.KollaImageBuilder([oc_tmpl_file])
result = tmpl_builder.container_images_from_template()
oc_yaml_file = os.path.join(files_dir, 'overcloud_containers.yaml')
yaml_builder = kb.KollaImageBuilder([oc_yaml_file])
container_images = yaml_builder.load_config_files(
yaml_builder.CONTAINER_IMAGES)
self.assertSequenceEqual(container_images, result)