Introduce cross-repository config templating
Make possible sharing and using of common parts of configs like keystone, db, messaging, etc as jinja templates (e.g. via macros) located at 'exports' directories of related repositories. Example of usage: ------------------------------------------------- share rabbitmq configuration as macros: ------------------------------------------------- # file fuel-ccp-rabbitmq/exports/messaging.j2 {% macro oslo_config() -%} [DEFAULT] transport_url=rabbit://{{ rabbitmq.user }} [oslo_messaging_rabbit] rabbit_ha_queues = true {%- endmacro %} ------------------------------------------------- use it in nova.conf.j2: ------------------------------------------------- # file fuel-ccp-nova/service/files/nova.conf.j2 [upgrade_levels] compute = auto {{ messaging.oslo_config() }} <----------- [wsgi] api_paste_config = /etc/nova/api-paste.ini ------------------------------------------------- During 'ccp deploy' the following occurs: - loading templates files from /exports/ dirs of avaliable repositories. - push files to k8s as ConfigMap with name 'exports'. - adding a container volume '/etc/ccp/macros' with the ConfigMap content - implicitly adding jinja imports of these templates files to all config files from /fuel-ccp-xxx/service/* to make possible macros usage. Change-Id: I4858d62a9713e90c09300f75e01e06a31d3ac0ae Depends-On: I429656b7eaf6312ee2d27ccaf0cb8802a234e871
This commit is contained in:
parent
631bf4dc62
commit
29133f2787
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
import socket
|
||||
|
||||
import jinja2
|
||||
|
@ -41,3 +42,17 @@ def jinja_render(path, context, functions=(), ignore_undefined=False):
|
|||
env.globals[func.__name__] = func
|
||||
content = env.get_template(os.path.basename(path)).render(context)
|
||||
return content
|
||||
|
||||
|
||||
def generate_jinja_imports(filenames):
|
||||
"""Generate str of jinja imports from list of filenames."""
|
||||
imports = [] # list of j2 imports: "{% import 'msg.j2' as msg %}"
|
||||
for name in filenames:
|
||||
import_as, extension = os.path.splitext(name) # remove file extension
|
||||
if not re.match('[a-zA-Z_][a-zA-Z0-9_]*', import_as):
|
||||
raise RuntimeError('Wrong templates file naming: the %s cannot be '
|
||||
'imported by jinja with %s name. Please use '
|
||||
'python compatible naming' % (name, import_as))
|
||||
imports.append(
|
||||
"{% import '" + name + "' as " + import_as + " with context %}")
|
||||
return ''.join(imports)
|
||||
|
|
|
@ -76,6 +76,27 @@ def address(service, port=None, external=False, with_scheme=False):
|
|||
return addr
|
||||
|
||||
|
||||
def get_repositories_exports(repos_names=None):
|
||||
"""Load shared templates from ./export dirs of the repositories. """
|
||||
exports = dict()
|
||||
repos_names = repos_names or [d['name'] for d in CONF.repositories.repos]
|
||||
for repo in repos_names:
|
||||
exports_dir = os.path.join(CONF.repositories.path, repo, 'exports')
|
||||
if os.path.exists(exports_dir) and os.path.isdir(exports_dir):
|
||||
for export in os.listdir(exports_dir):
|
||||
path = os.path.join(exports_dir, export)
|
||||
LOG.debug('Found shared jinja template file %s', path)
|
||||
if export not in exports:
|
||||
exports[export] = list()
|
||||
with open(path) as f:
|
||||
exports[export].append(f.read())
|
||||
|
||||
for export in exports:
|
||||
exports[export] = '\n'.join(exports[export])
|
||||
|
||||
return exports
|
||||
|
||||
|
||||
def get_deploy_components_info(rendering_context=None):
|
||||
if rendering_context is None:
|
||||
rendering_context = CONF.configs._dict
|
||||
|
|
|
@ -71,7 +71,7 @@ def process_files(files, service_dir):
|
|||
f["content"] = content
|
||||
|
||||
|
||||
def parse_role(component, topology, configmaps):
|
||||
def parse_role(component, topology, configmaps, jinja_imports):
|
||||
service_dir = component["service_dir"]
|
||||
role = component["service_content"]
|
||||
component_name = component["component_name"]
|
||||
|
@ -82,7 +82,8 @@ def parse_role(component, topology, configmaps):
|
|||
_expand_files(service, role.get("files"))
|
||||
|
||||
process_files(role.get("files"), service_dir)
|
||||
files_cm = _create_files_configmap(service_name, role.get("files"))
|
||||
files_cm = _create_files_configmap(service_name, role.get("files"),
|
||||
jinja_imports)
|
||||
meta_cm = _create_meta_configmap(service)
|
||||
|
||||
workflows = _parse_workflows(service)
|
||||
|
@ -311,13 +312,13 @@ def _create_start_script_configmap():
|
|||
return kubernetes.process_object(cm)
|
||||
|
||||
|
||||
def _create_files_configmap(service_name, files):
|
||||
def _create_files_configmap(service_name, files, macros_imports):
|
||||
configmap_name = "%s-%s" % (service_name, templates.FILES_CONFIG)
|
||||
data = {}
|
||||
if files:
|
||||
for filename, f in files.items():
|
||||
with open(f["content"], "r") as f:
|
||||
data[filename] = f.read()
|
||||
data[filename] = macros_imports + f.read()
|
||||
data["placeholder"] = ""
|
||||
template = templates.serialize_configmap(configmap_name, data)
|
||||
return kubernetes.process_object(template)
|
||||
|
@ -334,6 +335,13 @@ def _create_meta_configmap(service):
|
|||
return kubernetes.process_object(template)
|
||||
|
||||
|
||||
def _create_jinja_templates_configmap(templates_files):
|
||||
"""Create config map of files from fuel-ccp-repo/exports dirs."""
|
||||
serialized = templates.serialize_configmap(templates.EXPORTS_CONFIG,
|
||||
templates_files)
|
||||
return kubernetes.process_object(serialized)
|
||||
|
||||
|
||||
def _make_topology(nodes, roles, replicas):
|
||||
failed = False
|
||||
# TODO(sreshetniak): move it to validation
|
||||
|
@ -443,7 +451,8 @@ def check_images_change(objects):
|
|||
return False
|
||||
|
||||
|
||||
def create_upgrade_jobs(component_name, upgrade_data, configmaps, topology):
|
||||
def create_upgrade_jobs(component_name, upgrade_data, configmaps, topology,
|
||||
jinja_imports):
|
||||
from_version = upgrade_data['_meta']['from']
|
||||
to_version = upgrade_data['_meta']['to']
|
||||
component = upgrade_data['_meta']['component']
|
||||
|
@ -457,7 +466,7 @@ def create_upgrade_jobs(component_name, upgrade_data, configmaps, topology):
|
|||
step['files'] = {f: files[f] for f in step['files']}
|
||||
|
||||
process_files(files, component['service_dir'])
|
||||
_create_files_configmap(prefix, files)
|
||||
_create_files_configmap(prefix, files, jinja_imports)
|
||||
container = {
|
||||
"name": prefix,
|
||||
"pre": [],
|
||||
|
@ -541,14 +550,17 @@ def deploy_components(components_map, components):
|
|||
_create_namespace(CONF.configs)
|
||||
_create_globals_configmap(CONF.configs)
|
||||
start_script_cm = _create_start_script_configmap()
|
||||
configmaps = (start_script_cm,)
|
||||
|
||||
# Create cm with jinja config templates shared across all repositories.
|
||||
templates_files = utils.get_repositories_exports()
|
||||
jinja_imports = jinja_utils.generate_jinja_imports(templates_files.keys())
|
||||
templates_cm = _create_jinja_templates_configmap(templates_files)
|
||||
configmaps = (start_script_cm, templates_cm)
|
||||
|
||||
upgrading_components = {}
|
||||
for service_name in components:
|
||||
service = components_map[service_name]
|
||||
objects_gen = parse_role(service,
|
||||
topology=topology,
|
||||
configmaps=configmaps)
|
||||
objects_gen = parse_role(service, topology, configmaps, jinja_imports)
|
||||
objects = list(itertools.chain.from_iterable(objects_gen))
|
||||
component_name = service['component_name']
|
||||
do_upgrade = component_name in upgrading_components
|
||||
|
@ -578,8 +590,9 @@ def deploy_components(components_map, components):
|
|||
upgrading_components[component_name][service_name] = objects
|
||||
|
||||
for component_name, component_upg in upgrading_components.items():
|
||||
create_upgrade_jobs(component_name, component_upg, configmaps,
|
||||
topology)
|
||||
create_upgrade_jobs(component_name, component_upg,
|
||||
configmaps, topology,
|
||||
jinja_imports)
|
||||
|
||||
if 'keystone' in components:
|
||||
_create_openrc(CONF.configs)
|
||||
|
|
|
@ -11,6 +11,7 @@ SCRIPT_CONFIG = "start-script"
|
|||
FILES_CONFIG = "files"
|
||||
META_CONFIG = "meta"
|
||||
ROLE_CONFIG = "role"
|
||||
EXPORTS_CONFIG = "exports"
|
||||
|
||||
ENTRYPOINT_PATH = "/opt/ccp_start_script/bin/start_script.py"
|
||||
PYTHON_PATH = "/usr/bin/python"
|
||||
|
@ -68,6 +69,10 @@ def serialize_volume_mounts(container, for_job=None):
|
|||
"name": SCRIPT_CONFIG,
|
||||
"mountPath": "/opt/ccp_start_script/bin"
|
||||
},
|
||||
{
|
||||
"name": EXPORTS_CONFIG,
|
||||
"mountPath": "/etc/ccp/%s" % EXPORTS_CONFIG
|
||||
},
|
||||
{
|
||||
"name": FILES_CONFIG,
|
||||
"mountPath": "/etc/ccp/%s" % FILES_CONFIG
|
||||
|
@ -250,10 +255,16 @@ def serialize_volumes(service, for_job=None):
|
|||
"name": "%s-%s" % (service["name"], FILES_CONFIG),
|
||||
"items": file_items
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": EXPORTS_CONFIG,
|
||||
"configMap": {
|
||||
"name": EXPORTS_CONFIG,
|
||||
}
|
||||
}
|
||||
]
|
||||
volume_names = [GLOBAL_CONFIG, META_CONFIG, ROLE_CONFIG, SCRIPT_CONFIG,
|
||||
FILES_CONFIG]
|
||||
FILES_CONFIG, EXPORTS_CONFIG]
|
||||
for cont in itertools.chain(service["containers"], [for_job]):
|
||||
for v in cont.get("volumes", ()):
|
||||
if v["name"] in volume_names:
|
||||
|
|
|
@ -44,6 +44,7 @@ class TestDeploy(base.TestCase):
|
|||
{'mountPath': '/etc/ccp/meta', 'name': 'meta'},
|
||||
{'mountPath': '/opt/ccp_start_script/bin',
|
||||
'name': 'start-script'},
|
||||
{'mountPath': '/etc/ccp/exports', 'name': 'exports'},
|
||||
{'mountPath': '/etc/ccp/files', 'name': 'files'}
|
||||
],
|
||||
"readinessProbe": {
|
||||
|
|
Loading…
Reference in New Issue