2d0125ed40
When using the multiple nic-config templates in THT and in tripleo-ansible the ordering of networks in the network_data file matters. Add a a tag 'tripleo_net_idx' to neutron resources to keep track of the ordering. Depends-On: https://review.opendev.org/772795 Partial-Implements: blueprint network-data-v2-ports Change-Id: I0d39ea713ce1201b60f3def69f453792e402eb33
408 lines
18 KiB
Python
Executable File
408 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# 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.
|
|
|
|
import argparse
|
|
import jinja2
|
|
import os
|
|
import shutil
|
|
import six
|
|
import sys
|
|
import yaml
|
|
|
|
__tht_root_dir = os.path.dirname(os.path.dirname(__file__))
|
|
|
|
|
|
def _shutil_copy_if_not_same(src, dst):
|
|
"""Copy with shutil ignoring the same file errors."""
|
|
if hasattr(shutil, 'SameFileError'):
|
|
try:
|
|
shutil.copy(src, dst)
|
|
except shutil.SameFileError:
|
|
pass
|
|
else:
|
|
try:
|
|
shutil.copy(src, dst)
|
|
except Exception as ex:
|
|
if 'are the same file' in six.text_type(ex):
|
|
pass
|
|
else:
|
|
raise
|
|
|
|
|
|
def parse_opts(argv):
|
|
parser = argparse.ArgumentParser(
|
|
description='Configure host network interfaces using a JSON'
|
|
' config file format.')
|
|
parser.add_argument('-p', '--base_path', metavar='BASE_PATH',
|
|
help="""base path of templates to process.""",
|
|
default='.')
|
|
parser.add_argument('-r', '--roles-data', metavar='ROLES_DATA',
|
|
help="""relative path to the roles_data.yaml file.""",
|
|
default='roles_data.yaml')
|
|
parser.add_argument('-n', '--network-data', metavar='NETWORK_DATA',
|
|
help=("""relative path to the network_data.yaml """
|
|
"""file."""),
|
|
default='network_data.yaml')
|
|
parser.add_argument('--safe',
|
|
action='store_true',
|
|
help="""Enable safe mode (do not overwrite files).""",
|
|
default=False)
|
|
parser.add_argument('-o', '--output-dir', metavar='OUTPUT_DIR',
|
|
help="""Output dir for all the templates""",
|
|
default='')
|
|
parser.add_argument('-c', '--clean',
|
|
action='store_true',
|
|
help=("""clean the templates dir by deleting """
|
|
"""generated templates"""))
|
|
parser.add_argument('-d', '--dry-run',
|
|
action='store_true',
|
|
help=("""only output file names normally generated """
|
|
"""from j2 templates"""))
|
|
opts = parser.parse_args(argv[1:])
|
|
|
|
return opts
|
|
|
|
|
|
def _j2_render_to_file(j2_template, j2_data, outfile_name=None,
|
|
overwrite=True, dry_run=False):
|
|
yaml_f = outfile_name or j2_template.replace('.j2.yaml', '.yaml')
|
|
if dry_run:
|
|
amend = 'dry run processing'
|
|
else:
|
|
amend = 'rendering'
|
|
print('%s j2 template to file: %s' % (amend, outfile_name))
|
|
|
|
if not overwrite and os.path.exists(outfile_name):
|
|
print('ERROR: path already exists for file: %s' % outfile_name)
|
|
sys.exit(1)
|
|
|
|
# Search for templates relative to the current template path first
|
|
template_base = os.path.dirname(yaml_f)
|
|
j2_loader = \
|
|
jinja2.loaders.FileSystemLoader([template_base, __tht_root_dir])
|
|
|
|
try:
|
|
# Render the j2 template
|
|
template = jinja2.Environment(loader=j2_loader).from_string(
|
|
j2_template)
|
|
r_template = template.render(**j2_data)
|
|
except jinja2.exceptions.TemplateError as ex:
|
|
error_msg = ("Error rendering template %s : %s"
|
|
% (yaml_f, six.text_type(ex)))
|
|
print(error_msg)
|
|
raise Exception(error_msg)
|
|
if not dry_run:
|
|
with open(outfile_name, 'w') as out_f:
|
|
out_f.write(r_template)
|
|
|
|
|
|
def _set_tags_based_on_role_name(role_data):
|
|
for role in role_data:
|
|
role['tags'] = role.get('tags', [])
|
|
role_name = role.get('name', str())
|
|
|
|
if ((role_name.startswith('Compute') or role_name.startswith('HciCeph')
|
|
or role_name.startswith('DistributedCompute'))
|
|
and 'compute' not in role['tags']):
|
|
role['tags'].append('compute')
|
|
print("DEPRECATED: Role '%s' without the 'compute' tag "
|
|
"detected, the tag was added automatically. Please "
|
|
"add the 'compute' tag in roles data. The function to "
|
|
"automatically add tags based on role name will be "
|
|
"removed in the next release." % role_name)
|
|
if role_name.startswith('Ceph') and 'ceph' not in role['tags']:
|
|
role['tags'].append('ceph')
|
|
print("DEPRECATED: Role '%s' without the 'ceph' tag "
|
|
"detected, the tag was added automatically. Please "
|
|
"add the 'ceph' tag in roles data. The function to "
|
|
"automatically add tags based on role name will be "
|
|
"removed in the next release." % role_name)
|
|
if (role_name.startswith('ComputeOvsDpdk')
|
|
and 'ovsdpdk' not in role['tags']):
|
|
role['tags'].append('ovsdpdk')
|
|
print("DEPRECATED: Role '%s' without the 'ovsdpdk' tag "
|
|
"detected, the tag was added automatically. Please "
|
|
"add the 'ovsdpdk' tag in roles data. The function to "
|
|
"automatically add tags based on role name will be "
|
|
"removed in the next release." % role_name)
|
|
if ((role_name.startswith('ObjectStorage')
|
|
or role_name.startswith('BlockStorage')
|
|
or role_name.startswith('Ceph'))
|
|
and 'storage' not in role['tags']):
|
|
role['tags'].append('storage')
|
|
print("DEPRECATED: Role '%s' without the 'storage' tag "
|
|
"detected, the tag was added automatically. Please "
|
|
"add the 'storage' tag in roles data. The function to "
|
|
"automatically add tags based on role name will be "
|
|
"removed in the next release." % role_name)
|
|
|
|
|
|
def process_templates(template_path, role_data_path, output_dir,
|
|
network_data_path, overwrite, dry_run):
|
|
|
|
with open(role_data_path) as role_data_file:
|
|
role_data = yaml.safe_load(role_data_file)
|
|
|
|
with open(network_data_path) as network_data_file:
|
|
network_data = yaml.safe_load(network_data_file)
|
|
if network_data is None:
|
|
network_data = []
|
|
|
|
# Set internal network index key for each network, network resources
|
|
# are created with a tag tripleo_net_idx
|
|
for idx, net in enumerate(network_data):
|
|
network_data[idx].update({'idx': idx})
|
|
|
|
j2_excludes = {}
|
|
j2_excludes_path = os.path.join(template_path, 'j2_excludes.yaml')
|
|
if os.path.exists(j2_excludes_path):
|
|
with open(j2_excludes_path) as role_data_file:
|
|
j2_excludes = yaml.safe_load(role_data_file)
|
|
|
|
if output_dir and not os.path.isdir(output_dir):
|
|
if os.path.exists(output_dir):
|
|
raise RuntimeError('Output dir %s is not a directory' % output_dir)
|
|
os.mkdir(output_dir)
|
|
|
|
# TODO(hjensas): In next release remove the function to automatically add
|
|
# tags based on role name.
|
|
_set_tags_based_on_role_name(role_data)
|
|
|
|
role_names = [r.get('name') for r in role_data]
|
|
r_map = {}
|
|
for r in role_data:
|
|
r_map[r.get('name')] = r
|
|
|
|
n_map = {}
|
|
for n in network_data:
|
|
if (n.get('enabled') is not False):
|
|
n_map[n.get('name')] = n
|
|
if not n.get('name_lower'):
|
|
n_map[n.get('name')]['name_lower'] = n.get('name').lower()
|
|
else:
|
|
print("skipping %s network: network is disabled" % n.get('name'))
|
|
|
|
excl_templates = ['%s/%s' % (template_path, e)
|
|
for e in j2_excludes.get('name', [])]
|
|
|
|
if os.path.isdir(template_path):
|
|
for subdir, dirs, files in os.walk(template_path):
|
|
|
|
# NOTE(flaper87): Ignore hidden dirs as we don't
|
|
# generate templates for those.
|
|
# Note the slice assignment for `dirs` is necessary
|
|
# because we need to modify the *elements* in the
|
|
# dirs list rather than the reference to the list.
|
|
# This way we'll make sure os.walk will iterate over
|
|
# the shrunk list. os.walk doesn't have an API for
|
|
# filtering dirs at this point.
|
|
dirs[:] = [d for d in dirs if not d[0] == '.']
|
|
files = [f for f in files if not f[0] == '.']
|
|
|
|
# NOTE(flaper87): We could have used shutil.copytree
|
|
# but it requires the dst dir to not be present. This
|
|
# approach is safer as it doesn't require us to delete
|
|
# the output_dir in advance and it allows for running
|
|
# the command multiple times with the same output_dir.
|
|
out_dir = subdir
|
|
if output_dir:
|
|
if template_path != '.':
|
|
# strip out base path if not default
|
|
temp = out_dir.split(template_path)[1]
|
|
out_dir = temp[1:] if temp.startswith('/') else temp
|
|
out_dir = os.path.join(output_dir, out_dir)
|
|
if not os.path.exists(out_dir):
|
|
os.mkdir(out_dir)
|
|
|
|
# Ensure template is on its expected search path
|
|
# for upcoming parsing and rendering
|
|
for f in files:
|
|
if f.endswith('.j2') and output_dir:
|
|
_shutil_copy_if_not_same(os.path.join(subdir, f), out_dir)
|
|
|
|
for f in files:
|
|
file_path = os.path.join(subdir, f)
|
|
# We do three templating passes here:
|
|
# 1. *.role.j2.yaml - we template just the role name
|
|
# and create multiple files (one per role)
|
|
# 2 *.network.j2.yaml - we template the network name and
|
|
# data and create multiple files for networks and
|
|
# network ports (one per network)
|
|
# 3. *.j2.yaml - we template with all roles_data,
|
|
# and create one file common to all roles
|
|
if f.endswith('.role.j2.yaml'):
|
|
print("jinja2 rendering role template %s" % f)
|
|
with open(file_path) as j2_template:
|
|
template_data = j2_template.read()
|
|
print("jinja2 rendering roles %s" % ","
|
|
.join(role_names))
|
|
for role in role_names:
|
|
j2_data = {'role': r_map[role]}
|
|
out_f = "-".join(
|
|
[role.lower(),
|
|
os.path.basename(f).replace('.role.j2.yaml',
|
|
'.yaml')])
|
|
out_f_path = os.path.join(out_dir, out_f)
|
|
if ('network/config' in file_path and
|
|
r_map[role].get(
|
|
'deprecated_nic_config_name')):
|
|
d_name = r_map[role].get(
|
|
'deprecated_nic_config_name')
|
|
out_f_path = os.path.join(out_dir, d_name)
|
|
elif ('network/config' in file_path):
|
|
d_name = "%s.yaml" % role.lower()
|
|
out_f_path = os.path.join(out_dir, d_name)
|
|
if not (out_f_path in excl_templates):
|
|
if '{{role.name}}' in template_data:
|
|
j2_data = {'role': r_map[role],
|
|
'networks': network_data}
|
|
_j2_render_to_file(template_data, j2_data,
|
|
out_f_path, overwrite,
|
|
dry_run)
|
|
else:
|
|
# Backwards compatibility with templates
|
|
# that specify {{role}} vs {{role.name}}
|
|
j2_data = {'role': role,
|
|
'networks': network_data}
|
|
_j2_render_to_file(
|
|
template_data, j2_data,
|
|
out_f_path, overwrite, dry_run)
|
|
|
|
else:
|
|
print('skipping rendering of %s' % out_f_path)
|
|
|
|
elif f.endswith('.network.j2.yaml'):
|
|
print("jinja2 rendering network template %s" % f)
|
|
with open(file_path) as j2_template:
|
|
template_data = j2_template.read()
|
|
print("jinja2 rendering networks %s" % ",".join(n_map))
|
|
for network in n_map:
|
|
j2_data = {'network': n_map[network]}
|
|
# Output file names in "<name>.yaml" format
|
|
out_f = os.path.basename(f).replace('.network.j2.yaml',
|
|
'.yaml')
|
|
if os.path.dirname(file_path).endswith('ports'):
|
|
out_f = out_f.replace('port',
|
|
n_map[network]['name_lower'])
|
|
else:
|
|
out_f = out_f.replace('network',
|
|
n_map[network]['name_lower'])
|
|
out_f_path = os.path.join(out_dir, out_f)
|
|
if not (out_f_path in excl_templates):
|
|
_j2_render_to_file(template_data, j2_data,
|
|
out_f_path, overwrite, dry_run)
|
|
else:
|
|
print('skipping rendering of %s' % out_f_path)
|
|
|
|
elif f.endswith('.j2.yaml'):
|
|
print("jinja2 rendering normal template %s" % f)
|
|
with open(file_path) as j2_template:
|
|
template_data = j2_template.read()
|
|
j2_data = {'roles': role_data,
|
|
'networks': network_data}
|
|
out_f = os.path.basename(f).replace('.j2.yaml',
|
|
'.yaml')
|
|
out_f_path = os.path.join(out_dir, out_f)
|
|
_j2_render_to_file(template_data, j2_data, out_f_path,
|
|
overwrite, dry_run)
|
|
elif output_dir:
|
|
_shutil_copy_if_not_same(os.path.join(subdir, f), out_dir)
|
|
|
|
else:
|
|
print('Unexpected argument %s' % template_path)
|
|
|
|
|
|
def clean_templates(base_path, role_data_path, network_data_path):
|
|
|
|
def delete(f):
|
|
if os.path.exists(f):
|
|
print("Deleting %s" % f)
|
|
os.unlink(f)
|
|
|
|
for root, dirs, files in os.walk(base_path):
|
|
for f in files:
|
|
if f.endswith('.j2.yaml'):
|
|
rendered_path = os.path.join(
|
|
root, '%s.yaml' % f.split('.j2.yaml')[0])
|
|
delete(rendered_path)
|
|
|
|
with open(network_data_path) as network_data_file:
|
|
network_data = yaml.safe_load(network_data_file)
|
|
|
|
for network in network_data:
|
|
network_path = os.path.join(
|
|
'network', '%s.yaml' % network['name_lower'])
|
|
network_from_pool_path = os.path.join(
|
|
'network', '%s_from_pool.yaml' % network['name_lower'])
|
|
network_v6_path = os.path.join(
|
|
'network', '%s_v6.yaml' % network['name_lower'])
|
|
network_from_pool_v6_path = os.path.join(
|
|
'network', '%s_from_pool_v6.yaml' % network['name_lower'])
|
|
ports_path = os.path.join(
|
|
'network', 'ports', '%s.yaml' % network['name_lower'])
|
|
external_resource_ports_path = os.path.join(
|
|
'network', 'ports',
|
|
'external_resource_%s.yaml' % network['name_lower'])
|
|
external_resource_ports_v6_path = os.path.join(
|
|
'network', 'ports',
|
|
'external_resource_%s_v6.yaml' % network['name_lower'])
|
|
ports_from_pool_path = os.path.join(
|
|
'network', 'ports', '%s_from_pool.yaml' % network['name_lower'])
|
|
ports_v6_path = os.path.join(
|
|
'network', 'ports', '%s_v6.yaml' % network['name_lower'])
|
|
ports_from_pool_v6_path = os.path.join(
|
|
'network', 'ports', '%s_from_pool_v6.yaml' % network['name_lower'])
|
|
|
|
delete(network_path)
|
|
delete(network_from_pool_path)
|
|
delete(network_v6_path)
|
|
delete(network_from_pool_v6_path)
|
|
delete(ports_path)
|
|
delete(external_resource_ports_path)
|
|
delete(external_resource_ports_v6_path)
|
|
delete(ports_from_pool_path)
|
|
delete(ports_v6_path)
|
|
delete(ports_from_pool_v6_path)
|
|
|
|
with open(role_data_path) as role_data_file:
|
|
role_data = yaml.safe_load(role_data_file)
|
|
|
|
for role in role_data:
|
|
role_path = os.path.join(
|
|
'puppet', '%s-role.yaml' % role['name'].lower())
|
|
host_config_and_reboot_path = os.path.join(
|
|
'extraconfig', 'pre_network',
|
|
'%s-host_config_and_reboot.yaml' % role['name'].lower())
|
|
krb_service_principals_path = os.path.join(
|
|
'extraconfig', 'nova_metadata', 'krb-service-principals',
|
|
'%s-role.yaml' % role['name'].lower())
|
|
common_services_path = os.path.join(
|
|
'common', 'services', '%s-role.yaml' % role['name'].lower())
|
|
|
|
delete(role_path)
|
|
delete(host_config_and_reboot_path)
|
|
delete(krb_service_principals_path)
|
|
delete(common_services_path)
|
|
|
|
|
|
opts = parse_opts(sys.argv)
|
|
|
|
role_data_path = os.path.join(opts.base_path, opts.roles_data)
|
|
network_data_path = os.path.join(opts.base_path, opts.network_data)
|
|
|
|
if opts.clean:
|
|
clean_templates(opts.base_path, role_data_path, network_data_path)
|
|
else:
|
|
process_templates(opts.base_path, role_data_path, opts.output_dir,
|
|
network_data_path, (not opts.safe), opts.dry_run)
|