Merge "tool: convert heat network-config to ansible j2"

This commit is contained in:
Zuul 2021-02-04 03:28:07 +00:00 committed by Gerrit Code Review
commit c72909b35a
24 changed files with 3113 additions and 0 deletions

0
tools/__init__.py Normal file
View File

View File

@ -0,0 +1,512 @@
#!/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 collections
import openstack
import os
import re
import sys
import yaml
MIN_VIABLE_MTU_HEADER = (
"{% set mtu_list = [ctlplane_mtu] %}\n"
"{% for network in role_networks %}\n"
"{{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}\n"
"{%- endfor %}\n"
"{% set min_viable_mtu = mtu_list | max %}\n"
)
DUAL_MIN_VIABLE_MTU_HEADER = (
"{% set mtu_ctlplane_list = [ctlplane_mtu] %}\n"
"{% set mtu_dataplane_list = [] %}\n"
"{% for network in role_networks %}\n"
"{# This block resolves the minimum viable MTU for interfaces connected to #}\n" # noqa
"{# the dataplane network(s), which start by Tenant, and also bonds #}\n"
"{# and bridges that carry multiple VLANs. Each VLAN may have different MTU. #}\n" # noqa
"{# The bridge, bond or interface must have an MTU to allow the VLAN with the #}\n" # noqa
"{# largest MTU. #}\n"
"{% if network.startswith('Tenant') %}\n"
"{{ mtu_dataplane_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}\n" # noqa
"{# This block resolves the minimum viable MTU for interfaces connected to #}\n" # noqa
"{# the control plane network(s) (don't start by Tenant), and also bonds #}\n" # noqa
"{# and bridges that carry multiple VLANs. Each VLAN may have different MTU. #}\n" # noqa
"{# The bridge, bond or interface must have an MTU to allow the VLAN with the #}\n" # noqa
"{# largest MTU. #}\n"
"{% else %}\n"
"{{ mtu_ctlplane_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}\n" # noqa
"{%- endfor %}\n"
"{% set min_viable_mtu_ctlplane = mtu_ctlplane_list | max %}\n"
"{% set min_viable_mtu_dataplane = mtu_dataplane_list | max %}\n"
)
UNSUPPORTED_HEAT_INTRINSIC_FUNCTIONS = {
'get_file', 'get_resource', 'digest', 'repeat', 'resource_facade',
'str_replace', 'str_replace_strict', 'str_split', 'map_merge',
'map_replace', 'yaql', 'equals', 'if', 'not', 'and', 'or', 'filter',
'make_url', 'contains'
}
QUOTE_FIX = '%_fix_quote_%'
DBL_QUOTE_FIX = '%_double_fix_quote_%'
def parse_opts(argv):
parser = argparse.ArgumentParser(
description='Convert to Ansible Jinja2 NIC config templates.')
parser.add_argument('--stack',
metavar='STACK_NAME',
help='Name or ID of heat stack (default=overcloud)',
default='overcloud')
parser.add_argument('-n', '--networks_file',
metavar='<network_data.yaml>',
required=True,
help=('Configuration file describing the network '
'deployment.'))
parser.add_argument('-y', '--yes',
default=False,
action='store_true',
help='Overwrite existing files.')
parser.add_argument('template',
metavar='TEMPLATE_FILE',
help='Existing NIC config template to convert.')
opts = parser.parse_args(argv[1:])
return opts
class TemplateDumper(yaml.SafeDumper):
def represent_ordered_dict(self, data):
return self.represent_dict(data.items())
class TemplateLoader(yaml.SafeLoader):
def construct_mapping(self, node):
self.flatten_mapping(node)
return collections.OrderedDict(self.construct_pairs(node))
TemplateDumper.add_representer(collections.OrderedDict,
TemplateDumper.represent_ordered_dict)
TemplateLoader.add_constructor(yaml.BaseLoader,
TemplateLoader.construct_mapping)
class ConvertToAnsibleJ2(object):
def __init__(self, stack_env, networks_file):
self.stack_env = stack_env
self.param_to_var_map = self.create_param_to_var_map(networks_file)
self.hard_coded_parameters = list()
@staticmethod
def unwrap_j2_var(x):
"""Strip jinja2 brackets from string
When nesting ansible vars in jinja2 the brackets must be
removed. This also adds appropriate quote fix prefix and
suffix.
"""
is_ansible_var = False
if isinstance(x, str):
if x.startswith('{{ ') and x.endswith(' }}'):
is_ansible_var = True
x = x[3:]
x = x[:-3]
else:
raise RuntimeError(
'Unsupported type {} for method unwrap_j2_var'.format(type(x)))
if is_ansible_var:
return DBL_QUOTE_FIX + '{}'.format(x) + DBL_QUOTE_FIX
else:
return QUOTE_FIX + '{}'.format(x) + QUOTE_FIX
@staticmethod
def strip_j2_comment(x):
"""Strip jinja2 comment from string
When nesting hard-coded parameter conversions in jinja2 the
comment must be removed.
"""
return re.sub('{#.*#}', '', x)
def normalize_complex(self, old):
if isinstance(old, list):
new = list()
for i in old:
if isinstance(i, str):
new.append(self.strip_j2_comment(self.unwrap_j2_var(i)))
if isinstance(i, (bool, int)):
new.append(i)
if isinstance(i, (list, dict)):
new.append(self.normalize_complex(i))
elif isinstance(old, dict):
new = dict()
for k, v in old.items():
k = QUOTE_FIX + '{}'.format(k) + QUOTE_FIX
if isinstance(v, str):
new[k] = self.strip_j2_comment(self.unwrap_j2_var(v))
if isinstance(v, (bool, int)):
new[k] = v
if isinstance(v, (list, dict)):
new[k] = self.normalize_complex(v)
else:
raise RuntimeError(
'Unsupported type {} for method normalize_complex'.format(
type(old)))
return new
@staticmethod
def to_j2_var(x):
if not isinstance(x, str):
raise RuntimeError(
'Unsupported type {} for method to_j2_var'.format(type(x)))
return '{{{{ {} }}}}'.format(x)
@staticmethod
def quote_fix(x):
return QUOTE_FIX + '{}'.format(x) + QUOTE_FIX
def create_param_to_var_map(self, networks_file):
_map = {
'ControlPlaneIp': self.to_j2_var('ctlplane_ip'),
'ControlPlaneSubnetCidr': self.to_j2_var('ctlplane_subnet_cidr'),
'ControlPlaneMtu': self.to_j2_var('ctlplane_mtu'),
'ControlPlaneDefaultRoute': self.to_j2_var('ctlplane_gateway_ip'),
'ControlPlaneStaticRoutes': self.to_j2_var('ctlplane_host_routes'),
'DnsServers': self.to_j2_var('ctlplane_dns_nameservers'),
'DnsSearchDomains': self.to_j2_var('dns_search_domains'),
'NumDpdkInterfaceRxQueues':
self.to_j2_var('num_dpdk_interface_rx_queues'),
'BondInterfaceOvsOptions':
self.to_j2_var('bond_interface_ovs_options')
}
with open(networks_file, 'r') as f:
networks = yaml.safe_load(f.read())
for net in networks:
name = net['name']
name_lower = net.get('name_lower', net['name'].lower())
_map.update({
name + 'NetworkVlanID':
self.to_j2_var('{}_vlan_id'.format(name_lower)),
name + 'IpSubnet':
'/'.join([self.to_j2_var('{}_ip'.format(name_lower)),
self.to_j2_var('{}_cidr'.format(name_lower))]),
name + 'InterfaceDefaultRoute':
self.to_j2_var('{}_gateway_ip'.format(name_lower)),
name + 'InterfaceRoutes':
self.to_j2_var('{}_host_routes'.format(name_lower)),
name + 'Mtu':
self.to_j2_var('{}_mtu'.format(name_lower))
})
return _map
def convert_get_param(self, old):
param = old['get_param']
if isinstance(param, str):
if param in self.param_to_var_map:
return self.param_to_var_map[param]
elif param in self.stack_env.get('parameter_defaults', {}):
stack_value = self.stack_env['parameter_defaults'][param]
print('INFO - Custom Parameter {} was hard-coded in the '
'converted template using the value from the Heat stack '
'environment.\n'
' To parameterize the value an ansible var must be '
'added using the {{role.name}}ExtraGroupVars '
'THT interface and the template modified to use the '
'ansible var.'.format(param))
j2_comment = (
'{{# NOTE! Custom parameter {} was hard-coded in '
'the converted template. To parameterize use the '
'{{role.name}}ExtraGroupVars THT interface and update the '
'template to use an ansible var. #}}'.format(param))
self.hard_coded_parameters.append(param)
if isinstance(stack_value, str):
return self.quote_fix(stack_value + j2_comment)
else:
return stack_value
else:
print('WARNING - Manual intervention required. Unable to '
'convert get_param occurrence: {}'.format(old))
return self.quote_fix(
'NEED MANUAL CONVERSION: {}'.format(str(old)))
elif isinstance(param, list):
print('WARNING - can not convert get_param referencing values in '
'complex datastructures. Please review the Ansible Jinja2 '
'template and convert this manually.')
return self.quote_fix(
'NEED MANUAL CONVERSION: {}'.format(str(old)))
raise RuntimeError(
'Unexpected Type {} in get_param: {}'.format(type(param), old))
def convert_get_attr(self, old):
attr = old['get_attr']
if not isinstance(attr, list):
raise RuntimeError(
'Attributes for get_attr convertsion must of type list.')
if 'MinViableMtu' in attr:
return self.to_j2_var('min_viable_mtu')
elif 'MinViableMtuBondApi' in attr:
return self.to_j2_var('min_viable_mtu_ctlplane')
elif 'MinViableMtuBondData' in attr:
return self.to_j2_var('min_viable_mtu_dataplane')
else:
print('WARNING - only MinViableMtu and combined '
'MinViableMtuBondApi + MinViableMtuBondData attribute can '
'be converted. Please review the Ansible Jinja2 template '
'and convert this manually.')
return 'NEED MANUAL CONVERSION: {}'.format(str(old))
def convert_list_join(self, list_join_attrs):
to_join = list()
for x in list_join_attrs[1]:
to_join.append(self.recursive_convert(x))
return list_join_attrs[0].join(to_join)
def convert_list_concat(self, list_concat_attrs):
ansible_concat_tpl = "{} | flatten"
to_concatenate = list()
if not isinstance(list_concat_attrs, list):
raise RuntimeError('list_concat_args must be a list')
for x in list_concat_attrs:
to_concatenate.append(self.recursive_convert(x))
to_concatenate = self.normalize_complex(to_concatenate)
new = ansible_concat_tpl.format(to_concatenate)
return self.to_j2_var(new)
def convert_list_concat_unique(self, list_concat_unique_attrs):
ansible_concat_unique_tpl = "{} | flatten | unique"
to_concatenate = list()
if not isinstance(list_concat_unique_attrs, list):
raise RuntimeError('list_concat_unique_attrs must be a list')
for x in list_concat_unique_attrs:
to_concatenate.append(self.recursive_convert(x))
to_concatenate = self.normalize_complex(to_concatenate)
new = ansible_concat_unique_tpl.format(to_concatenate)
return self.to_j2_var(new)
@staticmethod
def convert_unsupported(old, fn):
print('WARNING - can not convert unsupported heat intrinsic function '
' {}. Please review the Ansible Jinja2 template and convert '
'this manually.'.format(fn))
return ('UNSUPPORTED HEAT INTRINSIC FUNCTION {} '
'REQUIRES MANUAL CONVERSION {}'.format(fn, old))
def recursive_convert(self, old):
if isinstance(old, (bool, str, int)):
new = old
elif isinstance(old, dict):
for fn in UNSUPPORTED_HEAT_INTRINSIC_FUNCTIONS:
if fn in old:
return self.convert_unsupported(old, fn)
if 'get_param' in old:
return self.convert_get_param(old)
elif 'get_attr' in old:
return self.convert_get_attr(old)
elif 'list_join' in old:
return self.convert_list_join(old['list_join'])
elif 'list_concat_unique' in old:
return self.convert_list_concat_unique(
old['list_concat_unique'])
else:
new = collections.OrderedDict()
for k, v in old.items():
if isinstance(v, (bool, str, int)):
new[k] = v
elif isinstance(v, (list, dict)):
new[k] = self.recursive_convert(v)
else:
raise RuntimeError(
'Unexpected Type {} for key: {}'.format(
type(v), k))
elif isinstance(old, list):
new = list()
for x in old:
new.append(self.recursive_convert(x))
else:
raise RuntimeError(
'Unknown type {} for recursive convert'.format(type(old)))
return new
def convert_template(self, template):
with open(template, 'r') as f:
heat_tpl = yaml.safe_load(f.read())
resources = set(heat_tpl['resources'].keys())
if not resources.issubset({'OsNetConfigImpl',
'MinViableMtu',
'MinViableMtuBondApi',
'MinViableMtuBondData'}):
msg = ('Only OsNetConfigImpl and MinViableMtu resoureces '
'supported. Found resources: {}'.format(resources))
raise RuntimeError(msg)
net_config_res = heat_tpl['resources'].get('OsNetConfigImpl')
mtu_header = None
if heat_tpl['resources'].get('MinViableMtu'):
mtu_header = 'single'
elif (heat_tpl['resources'].get('MinViableMtuBondApi')
and heat_tpl['resources'].get('MinViableMtuBondData')):
mtu_header = 'dual'
if not net_config_res:
raise RuntimeError('OsNetConfigImpl resource not found in '
'template.')
net_config_res_props = net_config_res['properties']
if net_config_res['type'] == 'OS::Heat::Value':
h_net_conf = net_config_res_props['value']
elif net_config_res['type'] == 'OS::Heat::SoftwareConfig':
h_net_conf = net_config_res_props['config']['str_replace'][
'params']['$network_config']['network_config']
else:
raise RuntimeError('No network config found in provided template.')
j2_config = collections.OrderedDict({'network_config': []})
j2_net_conf = j2_config['network_config']
j2_net_conf.extend(self.recursive_convert(h_net_conf))
j2_header = None
if self.hard_coded_parameters:
j2_header = (
'{# The values of the following custom heat parameters was '
'hard-coded into this template:\n')
for param in self.hard_coded_parameters:
j2_header += ' * {}\n'.format(param)
j2_header += (
'To parameterize use the {{role.name}}ExtraGroupVars THT '
'interface and update the template to use an ansible var.\n'
'#}\n')
return j2_config, mtu_header, j2_header
def write_j2_template(j2_template, j2_config, mtu_header, j2_header):
"""Write the Jinja2 template file
This is done in three steps because the YAML dumper insists
to add quotes:
write the template file
read the template file
re-write it with quotes removed
"""
with open(j2_template, 'w') as f:
if j2_header:
f.write(j2_header)
f.write('---\n')
if mtu_header:
if mtu_header == 'single':
f.write(MIN_VIABLE_MTU_HEADER)
elif mtu_header == 'dual':
f.write(DUAL_MIN_VIABLE_MTU_HEADER)
yaml.dump(j2_config, f, TemplateDumper, width=256,
default_flow_style=False)
with open(j2_template, 'r') as f:
data = f.read()
# Remove quote before jinja2 var reference
data = data.replace('\'{{', '{{')
# Remove quote after jinja2 var reference
data = data.replace('}}\'', '}}')
# Remove quote before value imported from stack environment
data = data.replace('\'' + QUOTE_FIX, '')
# Remove quote after value imported from stack environment
data = data.replace(QUOTE_FIX + '\'', '')
# Remove quote before value imported from stack environment
data = data.replace('\'\'' + DBL_QUOTE_FIX, '')
# Remove quote after value imported from stack environment
data = data.replace(DBL_QUOTE_FIX + '\'\'', '')
# Remove quote_fix string
data = data.replace(QUOTE_FIX, '')
data = data.replace(DBL_QUOTE_FIX, '')
with open(j2_template, 'w') as f:
f.write(data)
def validate_files(opts, template, networks_file, j2_template):
if not os.path.exists(template):
raise RuntimeError('Template file not found {}.'.format(template))
if not os.path.isfile(template):
raise RuntimeError('Template {} is not a file.'.format(template))
if not os.path.exists(networks_file):
raise RuntimeError('Networks file not found, {}.'.format(
networks_file))
if not os.path.isfile(networks_file):
raise RuntimeError('Networks file {} is not a file.'.format(
networks_file))
if os.path.exists(j2_template) and not opts.yes:
raise RuntimeError('Ansible Jinja2 template {} already exists'.format(
j2_template))
if os.path.exists(j2_template) and not os.path.isfile(j2_template):
raise RuntimeError('Existing {} is not a file.'.format(j2_template))
pass
def get_stack_environment(stack_name):
try:
conn = openstack.connect('undercloud')
stack = conn.orchestration.find_stack(stack_name)
if not stack:
print('INFO: Heat stack {} not found.'.format(stack_name))
return {}
stack_env = conn.orchestration.get_stack_environment(stack)
except Exception as e:
print('ERROR: Unable to get stack environment.')
raise e
return stack_env
def main():
opts = parse_opts(sys.argv)
template = os.path.abspath(opts.template)
networks_file = os.path.abspath(opts.networks_file)
j2_template = os.path.splitext(template)[0] + '.j2'
validate_files(opts, template, networks_file, j2_template)
stack_env = get_stack_environment(opts.stack)
converter = ConvertToAnsibleJ2(stack_env, networks_file)
j2_config, mtu_header, j2_header = converter.convert_template(template)
write_j2_template(j2_template, j2_config, mtu_header, j2_header)
if __name__ == '__main__':
main()

0
tools/tests/__init__.py Normal file
View File

View File

@ -0,0 +1,344 @@
heat_template_version: rocky
description: >
Software Config to drive os-net-config with 2 Linux bonds. One bond is on a
bridge with VLANs attached for the Controller role.
parameters:
ControlPlaneIp:
default: ''
description: IP address/subnet on the ctlplane network
type: string
ControlPlaneSubnetCidr:
default: ''
description: >
The subnet CIDR of the control plane network. (The parameter is
automatically resolved from the ctlplane subnet's cidr attribute.)
type: string
ControlPlaneDefaultRoute:
default: ''
description: The default route of the control plane network. (The parameter
is automatically resolved from the ctlplane subnet's gateway_ip attribute.)
type: string
ControlPlaneStaticRoutes:
default: []
description: >
Routes for the ctlplane network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ControlPlaneMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the network.
(The parameter is automatically resolved from the ctlplane network's mtu attribute.)
type: number
StorageIpSubnet:
default: ''
description: IP address/subnet on the storage network
type: string
StorageNetworkVlanID:
default: 1
description: Vlan ID for the storage network traffic.
type: number
StorageMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Storage network.
type: number
StorageInterfaceRoutes:
default: []
description: >
Routes for the storage network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
StorageMgmtIpSubnet:
default: ''
description: IP address/subnet on the storage_mgmt network
type: string
StorageMgmtNetworkVlanID:
default: 1
description: Vlan ID for the storage_mgmt network traffic.
type: number
StorageMgmtMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
StorageMgmt network.
type: number
StorageMgmtInterfaceRoutes:
default: []
description: >
Routes for the storage_mgmt network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
InternalApiIpSubnet:
default: ''
description: IP address/subnet on the internal_api network
type: string
InternalApiNetworkVlanID:
default: 1
description: Vlan ID for the internal_api network traffic.
type: number
InternalApiMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
InternalApi network.
type: number
InternalApiInterfaceRoutes:
default: []
description: >
Routes for the internal_api network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
TenantIpSubnet:
default: ''
description: IP address/subnet on the tenant network
type: string
TenantNetworkVlanID:
default: 1
description: Vlan ID for the tenant network traffic.
type: number
TenantMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Tenant network.
type: number
TenantInterfaceRoutes:
default: []
description: >
Routes for the tenant network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ExternalIpSubnet:
default: ''
description: IP address/subnet on the external network
type: string
ExternalNetworkVlanID:
default: 1
description: Vlan ID for the external network traffic.
type: number
ExternalMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
External network.
type: number
ExternalInterfaceDefaultRoute:
default: ''
description: default route for the external network
type: string
ExternalInterfaceRoutes:
default: []
description: >
Routes for the external network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
DnsServers: # Override this via parameter_defaults
default: []
description: >
DNS servers to use for the Overcloud (2 max for some implementations).
If not set the nameservers configured in the ctlplane subnet's
dns_nameservers attribute will be used.
type: comma_delimited_list
DnsSearchDomains: # Override this via parameter_defaults
default: []
description: A list of DNS search domains to be added (in order) to resolv.conf.
type: comma_delimited_list
BondInterfaceOvsOptions:
default: bond_mode=active-backup
description: 'The ovs_options or bonding_options string for the bond
interface. Set things like lacp=active and/or bond_mode=balance-slb
for OVS bonds or like mode=4 for Linux bonds using this option.'
type: string
resources:
MinViableMtuBondApi:
# This resource resolves the minimum viable MTU for interfaces, bonds and
# bridges that carry multiple VLANs. Each VLAN may have different MTU. The
# bridge, bond or interface must have an MTU to allow the VLAN with the
# largest MTU.
type: OS::Heat::Value
properties:
type: number
value:
yaql:
expression: $.data.max()
data:
- {get_param: ControlPlaneMtu}
- {get_param: StorageMtu}
- {get_param: StorageMgmtMtu}
- {get_param: InternalApiMtu}
- {get_param: ExternalMtu}
MinViableMtuBondData:
# This resource resolves the minimum viable MTU for interfaces, bonds and
# bridges that carry multiple VLANs. Each VLAN may have different MTU. The
# bridge, bond or interface must have an MTU to allow the VLAN with the
# largest MTU.
type: OS::Heat::Value
properties:
type: number
value:
yaql:
expression: $.data.max()
data:
- {get_param: ControlPlaneMtu}
- {get_param: TenantMtu}
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: ../../scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: interface
name: nic1
mtu:
get_param: ControlPlaneMtu
use_dhcp: false
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
routes:
list_concat_unique:
- get_param: ControlPlaneStaticRoutes
- type: linux_bond
name: bond_api
mtu:
get_attr: [MinViableMtuBondApi, value]
use_dhcp: false
bonding_options:
get_param: BondInterfaceOvsOptions
dns_servers:
get_param: DnsServers
domain:
get_param: DnsSearchDomains
members:
- type: interface
name: nic2
mtu:
get_attr: [MinViableMtuBondApi, value]
primary: true
- type: interface
name: nic3
mtu:
get_attr: [MinViableMtuBondApi, value]
- type: vlan
device: bond_api
mtu:
get_param: StorageMtu
vlan_id:
get_param: StorageNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageIpSubnet
routes:
list_concat_unique:
- get_param: StorageInterfaceRoutes
- type: vlan
device: bond_api
mtu:
get_param: StorageMgmtMtu
vlan_id:
get_param: StorageMgmtNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageMgmtIpSubnet
routes:
list_concat_unique:
- get_param: StorageMgmtInterfaceRoutes
- type: vlan
device: bond_api
mtu:
get_param: InternalApiMtu
vlan_id:
get_param: InternalApiNetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
routes:
list_concat_unique:
- get_param: InternalApiInterfaceRoutes
- type: vlan
device: bond_api
mtu:
get_param: ExternalMtu
vlan_id:
get_param: ExternalNetworkVlanID
addresses:
- ip_netmask:
get_param: ExternalIpSubnet
routes:
list_concat_unique:
- get_param: ExternalInterfaceRoutes
- - default: true
next_hop:
get_param: ExternalInterfaceDefaultRoute
- type: ovs_bridge
name: bridge_name
dns_servers:
get_param: DnsServers
members:
- type: linux_bond
name: bond-data
mtu:
get_attr: [MinViableMtuBondData, value]
bonding_options:
get_param: BondInterfaceOvsOptions
members:
- type: interface
name: nic4
mtu:
get_attr: [MinViableMtuBondData, value]
primary: true
- type: interface
name: nic5
mtu:
get_attr: [MinViableMtuBondData, value]
- type: vlan
device: bond-data
mtu:
get_param: TenantMtu
vlan_id:
get_param: TenantNetworkVlanID
addresses:
- ip_netmask:
get_param: TenantIpSubnet
routes:
list_concat_unique:
- get_param: TenantInterfaceRoutes
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,298 @@
heat_template_version: rocky
description: >
Software Config to drive os-net-config with 2 bonded nics on a bridge with VLANs attached for the Controller role.
parameters:
ControlPlaneIp:
default: ''
description: IP address/subnet on the ctlplane network
type: string
ControlPlaneSubnetCidr:
default: ''
description: >
The subnet CIDR of the control plane network. (The parameter is
automatically resolved from the ctlplane subnet's cidr attribute.)
type: string
ControlPlaneDefaultRoute:
default: ''
description: The default route of the control plane network. (The parameter
is automatically resolved from the ctlplane subnet's gateway_ip attribute.)
type: string
ControlPlaneStaticRoutes:
default: []
description: >
Routes for the ctlplane network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ControlPlaneMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the network.
(The parameter is automatically resolved from the ctlplane network's mtu attribute.)
type: number
StorageIpSubnet:
default: ''
description: IP address/subnet on the storage network
type: string
StorageNetworkVlanID:
default: 1
description: Vlan ID for the storage network traffic.
type: number
StorageMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Storage network.
type: number
StorageInterfaceRoutes:
default: []
description: >
Routes for the storage network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
StorageMgmtIpSubnet:
default: ''
description: IP address/subnet on the storage_mgmt network
type: string
StorageMgmtNetworkVlanID:
default: 1
description: Vlan ID for the storage_mgmt network traffic.
type: number
StorageMgmtMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
StorageMgmt network.
type: number
StorageMgmtInterfaceRoutes:
default: []
description: >
Routes for the storage_mgmt network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
InternalApiIpSubnet:
default: ''
description: IP address/subnet on the internal_api network
type: string
InternalApiNetworkVlanID:
default: 1
description: Vlan ID for the internal_api network traffic.
type: number
InternalApiMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
InternalApi network.
type: number
InternalApiInterfaceRoutes:
default: []
description: >
Routes for the internal_api network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
TenantIpSubnet:
default: ''
description: IP address/subnet on the tenant network
type: string
TenantNetworkVlanID:
default: 1
description: Vlan ID for the tenant network traffic.
type: number
TenantMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Tenant network.
type: number
TenantInterfaceRoutes:
default: []
description: >
Routes for the tenant network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ExternalIpSubnet:
default: ''
description: IP address/subnet on the external network
type: string
ExternalNetworkVlanID:
default: 1
description: Vlan ID for the external network traffic.
type: number
ExternalMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
External network.
type: number
ExternalInterfaceDefaultRoute:
default: ''
description: default route for the external network
type: string
ExternalInterfaceRoutes:
default: []
description: >
Routes for the external network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
DnsServers: # Override this via parameter_defaults
default: []
description: >
DNS servers to use for the Overcloud (2 max for some implementations).
If not set the nameservers configured in the ctlplane subnet's
dns_nameservers attribute will be used.
type: comma_delimited_list
DnsSearchDomains: # Override this via parameter_defaults
default: []
description: A list of DNS search domains to be added (in order) to resolv.conf.
type: comma_delimited_list
BondInterfaceOvsOptions:
default: bond_mode=active-backup
description: 'The ovs_options or bonding_options string for the bond
interface. Set things like lacp=active and/or bond_mode=balance-slb
for OVS bonds or like mode=4 for Linux bonds using this option.'
type: string
resources:
MinViableMtu:
# This resource resolves the minimum viable MTU for interfaces, bonds and
# bridges that carry multiple VLANs. Each VLAN may have different MTU. The
# bridge, bond or interface must have an MTU to allow the VLAN with the
# largest MTU.
type: OS::Heat::Value
properties:
type: number
value:
yaql:
expression: $.data.max()
data:
- {get_param: ControlPlaneMtu}
- {get_param: StorageMtu}
- {get_param: StorageMgmtMtu}
- {get_param: InternalApiMtu}
- {get_param: TenantMtu}
- {get_param: ExternalMtu}
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: ../../scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: interface
name: nic1
mtu:
get_param: ControlPlaneMtu
use_dhcp: false
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
routes:
list_concat_unique:
- get_param: ControlPlaneStaticRoutes
- type: ovs_bridge
name: bridge_name
dns_servers:
get_param: DnsServers
domain:
get_param: DnsSearchDomains
members:
- type: ovs_bond
name: bond1
mtu:
get_attr: [MinViableMtu, value]
ovs_options:
get_param: BondInterfaceOvsOptions
members:
- type: interface
name: nic2
mtu:
get_attr: [MinViableMtu, value]
primary: true
- type: interface
name: nic3
mtu:
get_attr: [MinViableMtu, value]
- type: vlan
mtu:
get_param: StorageMtu
vlan_id:
get_param: StorageNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageIpSubnet
routes:
list_concat_unique:
- get_param: StorageInterfaceRoutes
- type: vlan
mtu:
get_param: StorageMgmtMtu
vlan_id:
get_param: StorageMgmtNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageMgmtIpSubnet
routes:
list_concat_unique:
- get_param: StorageMgmtInterfaceRoutes
- type: vlan
mtu:
get_param: InternalApiMtu
vlan_id:
get_param: InternalApiNetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
routes:
list_concat_unique:
- get_param: InternalApiInterfaceRoutes
- type: vlan
mtu:
get_param: TenantMtu
vlan_id:
get_param: TenantNetworkVlanID
addresses:
- ip_netmask:
get_param: TenantIpSubnet
routes:
list_concat_unique:
- get_param: TenantInterfaceRoutes
- type: vlan
mtu:
get_param: ExternalMtu
vlan_id:
get_param: ExternalNetworkVlanID
addresses:
- ip_netmask:
get_param: ExternalIpSubnet
routes:
list_concat_unique:
- get_param: ExternalInterfaceRoutes
- - default: true
next_hop:
get_param: ExternalInterfaceDefaultRoute
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,239 @@
heat_template_version: rocky
parameters:
# Supernets
StorageSupernet:
type: string
StorageMgmtSupernet:
type: string
InternalApiSupernet:
type: string
TenantSupernet:
type: string
ExternalSupernet:
type: string
# Default Routes
ControlPlaneDefaultRoute:
type: string
ControlPlaneEdge1DefaultRoute:
type: string
ControlPlaneEdge2DefaultRoute:
type: string
StorageInterfaceDefaultRoute:
type: string
StorageEdge1InterfaceDefaultRoute:
type: string
StorageEdge2InterfaceDefaultRoute:
type: string
StorageMgmtInterfaceDefaultRoute:
type: string
StorageMgmtEdge1InterfaceDefaultRoute:
type: string
StorageMgmtEdge2InterfaceDefaultRoute:
type: string
InternalApiInterfaceDefaultRoute:
type: string
InternalApiEdge1InterfaceDefaultRoute:
type: string
InternalApiEdge2InterfaceDefaultRoute:
type: string
TenantInterfaceDefaultRoute:
type: string
TenantEdge1InterfaceDefaultRoute:
type: string
TenantEdge2InterfaceDefaultRoute:
type: string
ExternalInterfaceDefaultRoute:
type: string
# IP subnets
StorageIpSubnet:
default: ''
type: string
StorageEdge1IpSubnet:
default: ''
type: string
StorageEdge2IpSubnet:
default: ''
type: string
StorageMgmtIpSubnet:
default: ''
type: string
StorageMgmtEdge1IpSubnet:
default: ''
type: string
StorageMgmtEdge2IpSubnet:
default: ''
type: string
InternalApiIpSubnet:
default: ''
type: string
InternalApiEdge1IpSubnet:
default: ''
type: string
InternalApiEdge2IpSubnet:
default: ''
type: string
TenantIpSubnet:
default: ''
type: string
TenantEdge1IpSubnet:
default: ''
type: string
TenantEdge2IpSubnet:
default: ''
type: string
ExternalIpSubnet:
default: ''
type: string
ManagementIpSubnet:
default: ''
type: string
# VLAN IDs
StorageNetworkVlanID:
default: 121
type: number
StorageEdge1NetworkVlanID:
default: 131
type: number
StorageEdge2NetworkVlanID:
default: 141
type: number
StorageMgmtNetworkVlanID:
default: 122
type: number
StorageMgmtEdge1NetworkVlanID:
default: 132
type: number
StorageMgmtEdge2NetworkVlanID:
default: 142
type: number
InternalApiNetworkVlanID:
default: 123
type: number
InternalApiEdge1NetworkVlanID:
default: 133
type: number
InternalApiEdge2NetworkVlanID:
default: 143
type: number
TenantNetworkVlanID:
default: 124
type: number
TenantEdge1NetworkVlanID:
default: 134
type: number
TenantEdge2NetworkVlanID:
default: 144
type: number
ExternalNetworkVlanID:
default: 200
type: number
ManagementNetworkVlanID:
default: 300
type: number
# Subnet CIDR
ControlPlaneSubnetCidr:
type: string
ControlPlaneEdge1SubnetCidr:
type: string
ControlPlaneEdge2SubnetCidr:
type: string
ControlPlaneIp:
type: string
DnsServers:
type: comma_delimited_list
# EC2 metadata server IPs
EC2MetadataIp:
type: string
Edge1EC2MetadataIp:
type: string
Edge2EC2MetadataIp:
type: string
resources:
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: /usr/share/openstack-tripleo-heat-templates/network/scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: interface
name: nic1
use_dhcp: false
dns_servers:
get_param: DnsServers
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneEdge1SubnetCidr
routes:
routes:
list_concat_unique:
- default: true
next_hop:
get_param: ControlPlaneEdge1DefaultRoute
- - ip_netmask: 192.168.254.0/24
next_hop: 192.168.24.1
- - ip_netmask: 169.254.169.254/32
next_hop:
get_param: Edge1EC2MetadataIp
- type: ovs_bridge
name: br-ex
use_dhcp: false
members:
- type: interface
name: nic2
primary: true
- type: vlan
vlan_id:
get_param: StorageEdge1NetworkVlanID
addresses:
- ip_netmask:
get_param: StorageEdge1IpSubnet
routes:
- ip_netmask:
get_param: StorageSupernet
next_hop:
get_param: StorageEdge1InterfaceDefaultRoute
- type: vlan
vlan_id:
get_param: InternalApiEdge1NetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiEdge1IpSubnet
routes:
- ip_netmask:
get_param: InternalApiSupernet
next_hop:
get_param: InternalApiEdge1InterfaceDefaultRoute
- type: vlan
vlan_id:
get_param: TenantEdge1NetworkVlanID
addresses:
- ip_netmask:
get_param: TenantEdge1IpSubnet
routes:
- ip_netmask:
get_param: TenantSupernet
next_hop:
get_param: TenantEdge1InterfaceDefaultRoute
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,280 @@
heat_template_version: rocky
description: >
Software Config to drive os-net-config to configure multiple interfaces for the Controller role.
parameters:
ControlPlaneIp:
default: ''
description: IP address/subnet on the ctlplane network
type: string
ControlPlaneSubnetCidr:
default: ''
description: >
The subnet CIDR of the control plane network. (The parameter is
automatically resolved from the ctlplane subnet's cidr attribute.)
type: string
ControlPlaneDefaultRoute:
default: ''
description: The default route of the control plane network. (The parameter
is automatically resolved from the ctlplane subnet's gateway_ip attribute.)
type: string
ControlPlaneStaticRoutes:
default: []
description: >
Routes for the ctlplane network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ControlPlaneMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the network.
(The parameter is automatically resolved from the ctlplane network's mtu attribute.)
type: number
StorageIpSubnet:
default: ''
description: IP address/subnet on the storage network
type: string
StorageMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Storage network.
type: number
StorageInterfaceRoutes:
default: []
description: >
Routes for the storage network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
StorageMgmtIpSubnet:
default: ''
description: IP address/subnet on the storage_mgmt network
type: string
StorageMgmtMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
StorageMgmt network.
type: number
StorageMgmtInterfaceRoutes:
default: []
description: >
Routes for the storage_mgmt network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
InternalApiIpSubnet:
default: ''
description: IP address/subnet on the internal_api network
type: string
InternalApiMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
InternalApi network.
type: number
InternalApiInterfaceRoutes:
default: []
description: >
Routes for the internal_api network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
TenantIpSubnet:
default: ''
description: IP address/subnet on the tenant network
type: string
TenantMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Tenant network.
type: number
TenantInterfaceRoutes:
default: []
description: >
Routes for the tenant network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ExternalIpSubnet:
default: ''
description: IP address/subnet on the external network
type: string
ExternalMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
External network.
type: number
ExternalInterfaceDefaultRoute:
default: ''
description: default route for the external network
type: string
ExternalInterfaceRoutes:
default: []
description: >
Routes for the external network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
DnsServers: # Override this via parameter_defaults
default: []
description: >
DNS servers to use for the Overcloud (2 max for some implementations).
If not set the nameservers configured in the ctlplane subnet's
dns_nameservers attribute will be used.
type: comma_delimited_list
DnsSearchDomains: # Override this via parameter_defaults
default: []
description: A list of DNS search domains to be added (in order) to resolv.conf.
type: comma_delimited_list
resources:
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: ../../scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: interface
name: nic1
mtu:
get_param: ControlPlaneMtu
use_dhcp: false
dns_servers:
get_param: DnsServers
domain:
get_param: DnsSearchDomains
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
routes:
list_concat_unique:
- get_param: ControlPlaneStaticRoutes
- type: interface
name: nic2
mtu:
get_param: StorageMtu
use_dhcp: false
- type: vlan
device: nic2
mtu:
get_param: StorageMtu
vlan_id:
get_param: StorageNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageIpSubnet
routes:
list_concat_unique:
- get_param: StorageInterfaceRoutes
- type: interface
name: nic5
mtu:
get_param: StorageMgmtMtu
use_dhcp: false
- type: vlan
device: nic5
mtu:
get_param: StorageMgmtMtu
vlan_id:
get_param: StorageMgmtNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageMgmtIpSubnet
routes:
list_concat_unique:
- get_param: StorageMgmtInterfaceRoutes
- type: interface
name: nic8
mtu:
get_param: InternalApiMtu
use_dhcp: false
- type: vlan
device: nic8
mtu:
get_param: InternalApiMtu
vlan_id:
get_param: InternalApiNetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
routes:
list_concat_unique:
- get_param: InternalApiInterfaceRoutes
- type: ovs_bridge
name: br-tenant
mtu:
get_param: TenantMtu
dns_servers:
get_param: DnsServers
use_dhcp: false
members:
- type: interface
name: nic11
mtu:
get_param: TenantMtu
use_dhcp: false
primary: true
- type: vlan
mtu:
get_param: TenantMtu
vlan_id:
get_param: TenantNetworkVlanID
addresses:
- ip_netmask:
get_param: TenantIpSubnet
routes:
list_concat_unique:
- get_param: TenantInterfaceRoutes
- type: ovs_bridge
name: bridge_name
mtu:
get_param: ExternalMtu
dns_servers:
get_param: DnsServers
use_dhcp: false
members:
- type: interface
name: nic14
mtu:
get_param: ExternalMtu
use_dhcp: false
primary: true
- type: vlan
mtu:
get_param: ExternalMtu
vlan_id:
get_param: ExternalNetworkVlanID
addresses:
- ip_netmask:
get_param: ExternalIpSubnet
routes:
list_concat_unique:
- get_param: ExternalInterfaceRoutes
- - default: true
next_hop:
get_param: ExternalInterfaceDefaultRoute
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,51 @@
resources:
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: /usr/share/openstack-tripleo-heat-templates/network/scripts/run-os-net-config.sh
params:
$network_config:
network_config:
# NIC 1 - Provisioning
- type: interface
name: nic1
use_dhcp: false
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
# NIC 2 - Control Group
- type: interface
name: nic2
use_dhcp: false
- type: vlan
device: nic2
vlan_id:
get_param: InternalApiNetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
# NIC 3 - Data Group
- type: ovs_bridge
name: bridge_name
dns_servers:
get_param: DnsServers
members:
- type: interface
name: nic3
primary: true
- type: vlan
vlan_id:
get_param: TenantNetworkVlanID
addresses:
- ip_netmask:
get_param: TenantIpSubnet

View File

@ -0,0 +1,285 @@
heat_template_version: rocky
description: >
Software Config to drive os-net-config to configure VLANs for the Controller role.
parameters:
ControlPlaneIp:
default: ''
description: IP address/subnet on the ctlplane network
type: string
ControlPlaneSubnetCidr:
default: ''
description: >
The subnet CIDR of the control plane network. (The parameter is
automatically resolved from the ctlplane subnet's cidr attribute.)
type: string
ControlPlaneDefaultRoute:
default: ''
description: The default route of the control plane network. (The parameter
is automatically resolved from the ctlplane subnet's gateway_ip attribute.)
type: string
ControlPlaneStaticRoutes:
default: []
description: >
Routes for the ctlplane network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ControlPlaneMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the network.
(The parameter is automatically resolved from the ctlplane network's mtu attribute.)
type: number
StorageIpSubnet:
default: ''
description: IP address/subnet on the storage network
type: string
StorageNetworkVlanID:
default: 1
description: Vlan ID for the storage network traffic.
type: number
StorageMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Storage network.
type: number
StorageInterfaceRoutes:
default: []
description: >
Routes for the storage network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
StorageMgmtIpSubnet:
default: ''
description: IP address/subnet on the storage_mgmt network
type: string
StorageMgmtNetworkVlanID:
default: 1
description: Vlan ID for the storage_mgmt network traffic.
type: number
StorageMgmtMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
StorageMgmt network.
type: number
StorageMgmtInterfaceRoutes:
default: []
description: >
Routes for the storage_mgmt network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
InternalApiIpSubnet:
default: ''
description: IP address/subnet on the internal_api network
type: string
InternalApiNetworkVlanID:
default: 1
description: Vlan ID for the internal_api network traffic.
type: number
InternalApiMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
InternalApi network.
type: number
InternalApiInterfaceRoutes:
default: []
description: >
Routes for the internal_api network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
TenantIpSubnet:
default: ''
description: IP address/subnet on the tenant network
type: string
TenantNetworkVlanID:
default: 1
description: Vlan ID for the tenant network traffic.
type: number
TenantMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Tenant network.
type: number
TenantInterfaceRoutes:
default: []
description: >
Routes for the tenant network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ExternalIpSubnet:
default: ''
description: IP address/subnet on the external network
type: string
ExternalNetworkVlanID:
default: 1
description: Vlan ID for the external network traffic.
type: number
ExternalMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
External network.
type: number
ExternalInterfaceDefaultRoute:
default: ''
description: default route for the external network
type: string
ExternalInterfaceRoutes:
default: []
description: >
Routes for the external network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
DnsServers: # Override this via parameter_defaults
default: []
description: >
DNS servers to use for the Overcloud (2 max for some implementations).
If not set the nameservers configured in the ctlplane subnet's
dns_nameservers attribute will be used.
type: comma_delimited_list
DnsSearchDomains: # Override this via parameter_defaults
default: []
description: A list of DNS search domains to be added (in order) to resolv.conf.
type: comma_delimited_list
resources:
MinViableMtu:
# This resource resolves the minimum viable MTU for interfaces, bonds and
# bridges that carry multiple VLANs. Each VLAN may have different MTU. The
# bridge, bond or interface must have an MTU to allow the VLAN with the
# largest MTU.
type: OS::Heat::Value
properties:
type: number
value:
yaql:
expression: $.data.max()
data:
- {get_param: ControlPlaneMtu}
- {get_param: StorageMtu}
- {get_param: StorageMgmtMtu}
- {get_param: InternalApiMtu}
- {get_param: TenantMtu}
- {get_param: ExternalMtu}
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: ../../scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: linux_bridge
name: bridge_name
mtu:
get_attr: [MinViableMtu, value]
use_dhcp: false
dns_servers:
get_param: DnsServers
domain:
get_param: DnsSearchDomains
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
routes:
list_concat_unique:
- get_param: ControlPlaneStaticRoutes
members:
- type: interface
name: interface_name
mtu:
get_attr: [MinViableMtu, value]
primary: true
- type: vlan
mtu:
get_param: StorageMtu
vlan_id:
get_param: StorageNetworkVlanID
device: bridge_name
addresses:
- ip_netmask:
get_param: StorageIpSubnet
routes:
list_concat_unique:
- get_param: StorageInterfaceRoutes
- type: vlan
mtu:
get_param: StorageMgmtMtu
vlan_id:
get_param: StorageMgmtNetworkVlanID
device: bridge_name
addresses:
- ip_netmask:
get_param: StorageMgmtIpSubnet
routes:
list_concat_unique:
- get_param: StorageMgmtInterfaceRoutes
- type: vlan
mtu:
get_param: InternalApiMtu
vlan_id:
get_param: InternalApiNetworkVlanID
device: bridge_name
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
routes:
list_concat_unique:
- get_param: InternalApiInterfaceRoutes
- type: vlan
mtu:
get_param: TenantMtu
vlan_id:
get_param: TenantNetworkVlanID
device: bridge_name
addresses:
- ip_netmask:
get_param: TenantIpSubnet
routes:
list_concat_unique:
- get_param: TenantInterfaceRoutes
- type: vlan
mtu:
get_param: ExternalMtu
vlan_id:
get_param: ExternalNetworkVlanID
device: bridge_name
addresses:
- ip_netmask:
get_param: ExternalIpSubnet
routes:
list_concat_unique:
- get_param: ExternalInterfaceRoutes
- - default: true
next_hop:
get_param: ExternalInterfaceDefaultRoute
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,281 @@
heat_template_version: rocky
description: >
Software Config to drive os-net-config to configure VLANs for the Controller role.
parameters:
ControlPlaneIp:
default: ''
description: IP address/subnet on the ctlplane network
type: string
ControlPlaneSubnetCidr:
default: ''
description: >
The subnet CIDR of the control plane network. (The parameter is
automatically resolved from the ctlplane subnet's cidr attribute.)
type: string
ControlPlaneDefaultRoute:
default: ''
description: The default route of the control plane network. (The parameter
is automatically resolved from the ctlplane subnet's gateway_ip attribute.)
type: string
ControlPlaneStaticRoutes:
default: []
description: >
Routes for the ctlplane network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ControlPlaneMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the network.
(The parameter is automatically resolved from the ctlplane network's mtu attribute.)
type: number
StorageIpSubnet:
default: ''
description: IP address/subnet on the storage network
type: string
StorageNetworkVlanID:
default: 1
description: Vlan ID for the storage network traffic.
type: number
StorageMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Storage network.
type: number
StorageInterfaceRoutes:
default: []
description: >
Routes for the storage network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
StorageMgmtIpSubnet:
default: ''
description: IP address/subnet on the storage_mgmt network
type: string
StorageMgmtNetworkVlanID:
default: 1
description: Vlan ID for the storage_mgmt network traffic.
type: number
StorageMgmtMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
StorageMgmt network.
type: number
StorageMgmtInterfaceRoutes:
default: []
description: >
Routes for the storage_mgmt network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
InternalApiIpSubnet:
default: ''
description: IP address/subnet on the internal_api network
type: string
InternalApiNetworkVlanID:
default: 1
description: Vlan ID for the internal_api network traffic.
type: number
InternalApiMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
InternalApi network.
type: number
InternalApiInterfaceRoutes:
default: []
description: >
Routes for the internal_api network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
TenantIpSubnet:
default: ''
description: IP address/subnet on the tenant network
type: string
TenantNetworkVlanID:
default: 1
description: Vlan ID for the tenant network traffic.
type: number
TenantMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
Tenant network.
type: number
TenantInterfaceRoutes:
default: []
description: >
Routes for the tenant network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
ExternalIpSubnet:
default: ''
description: IP address/subnet on the external network
type: string
ExternalNetworkVlanID:
default: 1
description: Vlan ID for the external network traffic.
type: number
ExternalMtu:
default: 1500
description: The maximum transmission unit (MTU) size(in bytes) that is
guaranteed to pass through the data path of the segments in the
External network.
type: number
ExternalInterfaceDefaultRoute:
default: ''
description: default route for the external network
type: string
ExternalInterfaceRoutes:
default: []
description: >
Routes for the external network traffic.
JSON route e.g. [{'destination':'10.0.0.0/16', 'nexthop':'10.0.0.1'}]
Unless the default is changed, the parameter is automatically resolved
from the subnet host_routes attribute.
type: json
DnsServers: # Override this via parameter_defaults
default: []
description: >
DNS servers to use for the Overcloud (2 max for some implementations).
If not set the nameservers configured in the ctlplane subnet's
dns_nameservers attribute will be used.
type: comma_delimited_list
DnsSearchDomains: # Override this via parameter_defaults
default: []
description: A list of DNS search domains to be added (in order) to resolv.conf.
type: comma_delimited_list
resources:
MinViableMtu:
# This resource resolves the minimum viable MTU for interfaces, bonds and
# bridges that carry multiple VLANs. Each VLAN may have different MTU. The
# bridge, bond or interface must have an MTU to allow the VLAN with the
# largest MTU.
type: OS::Heat::Value
properties:
type: number
value:
yaql:
expression: $.data.max()
data:
- {get_param: ControlPlaneMtu}
- {get_param: StorageMtu}
- {get_param: StorageMgmtMtu}
- {get_param: InternalApiMtu}
- {get_param: TenantMtu}
- {get_param: ExternalMtu}
OsNetConfigImpl:
type: OS::Heat::SoftwareConfig
properties:
group: script
config:
str_replace:
template:
get_file: ../../scripts/run-os-net-config.sh
params:
$network_config:
network_config:
- type: ovs_bridge
name: bridge_name
mtu:
get_attr: [MinViableMtu, value]
use_dhcp: false
dns_servers:
get_param: DnsServers
domain:
get_param: DnsSearchDomains
addresses:
- ip_netmask:
list_join:
- /
- - get_param: ControlPlaneIp
- get_param: ControlPlaneSubnetCidr
routes:
list_concat_unique:
- get_param: ControlPlaneStaticRoutes
members:
- type: interface
name: nic1
mtu:
get_attr: [MinViableMtu, value]
# force the MAC address of the bridge to this interface
primary: true
- type: vlan
mtu:
get_param: StorageMtu
vlan_id:
get_param: StorageNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageIpSubnet
routes:
list_concat_unique:
- get_param: StorageInterfaceRoutes
- type: vlan
mtu:
get_param: StorageMgmtMtu
vlan_id:
get_param: StorageMgmtNetworkVlanID
addresses:
- ip_netmask:
get_param: StorageMgmtIpSubnet
routes:
list_concat_unique:
- get_param: StorageMgmtInterfaceRoutes
- type: vlan
mtu:
get_param: InternalApiMtu
vlan_id:
get_param: InternalApiNetworkVlanID
addresses:
- ip_netmask:
get_param: InternalApiIpSubnet
routes:
list_concat_unique:
- get_param: InternalApiInterfaceRoutes
- type: vlan
mtu:
get_param: TenantMtu
vlan_id:
get_param: TenantNetworkVlanID
addresses:
- ip_netmask:
get_param: TenantIpSubnet
routes:
list_concat_unique:
- get_param: TenantInterfaceRoutes
- type: vlan
mtu:
get_param: ExternalMtu
vlan_id:
get_param: ExternalNetworkVlanID
addresses:
- ip_netmask:
get_param: ExternalIpSubnet
routes:
list_concat_unique:
- get_param: ExternalInterfaceRoutes
- - default: true
next_hop:
get_param: ExternalInterfaceDefaultRoute
outputs:
OS::stack_id:
description: The OsNetConfigImpl resource.
value:
get_resource: OsNetConfigImpl

View File

@ -0,0 +1,95 @@
---
{% set mtu_ctlplane_list = [ctlplane_mtu] %}
{% set mtu_dataplane_list = [] %}
{% for network in role_networks %}
{# This block resolves the minimum viable MTU for interfaces connected to #}
{# the dataplane network(s), which start by Tenant, and also bonds #}
{# and bridges that carry multiple VLANs. Each VLAN may have different MTU. #}
{# The bridge, bond or interface must have an MTU to allow the VLAN with the #}
{# largest MTU. #}
{% if network.startswith('Tenant') %}
{{ mtu_dataplane_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}
{# This block resolves the minimum viable MTU for interfaces connected to #}
{# the control plane network(s) (don't start by Tenant), and also bonds #}
{# and bridges that carry multiple VLANs. Each VLAN may have different MTU. #}
{# The bridge, bond or interface must have an MTU to allow the VLAN with the #}
{# largest MTU. #}
{% else %}
{{ mtu_ctlplane_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}
{%- endfor %}
{% set min_viable_mtu_ctlplane = mtu_ctlplane_list | max %}
{% set min_viable_mtu_dataplane = mtu_dataplane_list | max %}
network_config:
- type: interface
name: nic1
mtu: {{ ctlplane_mtu }}
use_dhcp: false
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
routes: {{ [ctlplane_host_routes] | flatten | unique }}
- type: linux_bond
name: bond_api
mtu: {{ min_viable_mtu_ctlplane }}
use_dhcp: false
bonding_options: {{ bond_interface_ovs_options }}
dns_servers: {{ ctlplane_dns_nameservers }}
domain: {{ dns_search_domains }}
members:
- type: interface
name: nic2
mtu: {{ min_viable_mtu_ctlplane }}
primary: true
- type: interface
name: nic3
mtu: {{ min_viable_mtu_ctlplane }}
- type: vlan
device: bond_api
mtu: {{ storage_mtu }}
vlan_id: {{ storage_vlan_id }}
addresses:
- ip_netmask: {{ storage_ip }}/{{ storage_cidr }}
routes: {{ [storage_host_routes] | flatten | unique }}
- type: vlan
device: bond_api
mtu: {{ storage_mgmt_mtu }}
vlan_id: {{ storage_mgmt_vlan_id }}
addresses:
- ip_netmask: {{ storage_mgmt_ip }}/{{ storage_mgmt_cidr }}
routes: {{ [storage_mgmt_host_routes] | flatten | unique }}
- type: vlan
device: bond_api
mtu: {{ internal_api_mtu }}
vlan_id: {{ internal_api_vlan_id }}
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
routes: {{ [internal_api_host_routes] | flatten | unique }}
- type: vlan
device: bond_api
mtu: {{ external_mtu }}
vlan_id: {{ external_vlan_id }}
addresses:
- ip_netmask: {{ external_ip }}/{{ external_cidr }}
routes: {{ [external_host_routes, [{'default': True, 'next_hop': external_gateway_ip}]] | flatten | unique }}
- type: ovs_bridge
name: bridge_name
dns_servers: {{ ctlplane_dns_nameservers }}
members:
- type: linux_bond
name: bond-data
mtu: {{ min_viable_mtu_dataplane }}
bonding_options: {{ bond_interface_ovs_options }}
members:
- type: interface
name: nic4
mtu: {{ min_viable_mtu_dataplane }}
primary: true
- type: interface
name: nic5
mtu: {{ min_viable_mtu_dataplane }}
- type: vlan
device: bond-data
mtu: {{ tenant_mtu }}
vlan_id: {{ tenant_vlan_id }}
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}
routes: {{ [tenant_host_routes] | flatten | unique }}

View File

@ -0,0 +1,61 @@
---
{% set mtu_list = [ctlplane_mtu] %}
{% for network in role_networks %}
{{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}
{%- endfor %}
{% set min_viable_mtu = mtu_list | max %}
network_config:
- type: interface
name: nic1
mtu: {{ ctlplane_mtu }}
use_dhcp: false
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
routes: {{ [ctlplane_host_routes] | flatten | unique }}
- type: ovs_bridge
name: bridge_name
dns_servers: {{ ctlplane_dns_nameservers }}
domain: {{ dns_search_domains }}
members:
- type: ovs_bond
name: bond1
mtu: {{ min_viable_mtu }}
ovs_options: {{ bond_interface_ovs_options }}
members:
- type: interface
name: nic2
mtu: {{ min_viable_mtu }}
primary: true
- type: interface
name: nic3
mtu: {{ min_viable_mtu }}
- type: vlan
mtu: {{ storage_mtu }}
vlan_id: {{ storage_vlan_id }}
addresses:
- ip_netmask: {{ storage_ip }}/{{ storage_cidr }}
routes: {{ [storage_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ storage_mgmt_mtu }}
vlan_id: {{ storage_mgmt_vlan_id }}
addresses:
- ip_netmask: {{ storage_mgmt_ip }}/{{ storage_mgmt_cidr }}
routes: {{ [storage_mgmt_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ internal_api_mtu }}
vlan_id: {{ internal_api_vlan_id }}
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
routes: {{ [internal_api_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ tenant_mtu }}
vlan_id: {{ tenant_vlan_id }}
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}
routes: {{ [tenant_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ external_mtu }}
vlan_id: {{ external_vlan_id }}
addresses:
- ip_netmask: {{ external_ip }}/{{ external_cidr }}
routes: {{ [external_host_routes, [{'default': True, 'next_hop': external_gateway_ip}]] | flatten | unique }}

View File

@ -0,0 +1,48 @@
{# The values of the following custom heat parameters was hard-coded into this template:
* ControlPlaneEdge1SubnetCidr
* ControlPlaneEdge1DefaultRoute
* Edge1EC2MetadataIp
* StorageSupernet
* InternalApiSupernet
* TenantSupernet
To parameterize use the {{role.name}}ExtraGroupVars THT interface and update the template to use an ansible var.
#}
---
network_config:
- type: interface
name: nic1
use_dhcp: false
dns_servers: {{ ctlplane_dns_nameservers }}
addresses:
- ip_netmask: {{ ctlplane_ip }}/24{# NOTE! Custom parameter ControlPlaneEdge1SubnetCidr was hard-coded in the converted template. To parameterize use the {role.name}ExtraGroupVars THT interface and update the template to use an ansible var. #}
routes: {{ [{'default': True, 'next_hop': '172.16.1.201'}, [{'ip_netmask': '192.168.254.0/24',
'next_hop': '192.168.24.1'}], [{'ip_netmask': '169.254.169.254/32', 'next_hop': '172.16.1.201'}]]
| flatten | unique }}
- type: ovs_bridge
name: br-ex
use_dhcp: false
members:
- type: interface
name: nic2
primary: true
- type: vlan
vlan_id: {{ storageedge1_vlan_id }}
addresses:
- ip_netmask: {{ storageedge1_ip }}/{{ storageedge1_cidr }}
routes:
- ip_netmask: 172.18.0.0/16{# NOTE! Custom parameter StorageSupernet was hard-coded in the converted template. To parameterize use the {role.name}ExtraGroupVars THT interface and update the template to use an ansible var. #}
next_hop: {{ storageedge1_gateway_ip }}
- type: vlan
vlan_id: {{ internal_apiedge1_vlan_id }}
addresses:
- ip_netmask: {{ internal_apiedge1_ip }}/{{ internal_apiedge1_cidr }}
routes:
- ip_netmask: 172.20.0.0/16{# NOTE! Custom parameter InternalApiSupernet was hard-coded in the converted template. To parameterize use the {role.name}ExtraGroupVars THT interface and update the template to use an ansible var. #}
next_hop: {{ internal_apiedge1_gateway_ip }}
- type: vlan
vlan_id: {{ tenantedge1_vlan_id }}
addresses:
- ip_netmask: {{ tenantedge1_ip }}/{{ tenantedge1_cidr }}
routes:
- ip_netmask: 172.21.0.0/16{# NOTE! Custom parameter TenantSupernet was hard-coded in the converted template. To parameterize use the {role.name}ExtraGroupVars THT interface and update the template to use an ansible var. #}
next_hop: {{ tenantedge1_gateway_ip }}

View File

@ -0,0 +1,39 @@
---
network_config:
- type: interface
name: nic1
use_dhcp: false
dns_servers: {{ ctlplane_dns_nameservers }}
addresses:
- ip_netmask: {{ ctlplane_ip }}/NEED MANUAL CONVERSION: {''get_param'': ''ControlPlaneEdge1SubnetCidr''}
routes: {{ [{'default': True, 'next_hop': "NEED MANUAL CONVERSION: {''get_param'': ''ControlPlaneEdge1DefaultRoute''}"}, [{'ip_netmask':
'192.168.254.0/24', 'next_hop': '192.168.24.1'}], [{'ip_netmask': '169.254.169.254/32', 'next_hop':
"NEED MANUAL CONVERSION: {''get_param'': ''Edge1EC2MetadataIp''}"}]] | flatten | unique }}
- type: ovs_bridge
name: br-ex
use_dhcp: false
members:
- type: interface
name: nic2
primary: true
- type: vlan
vlan_id: {{ storageedge1_vlan_id }}
addresses:
- ip_netmask: {{ storageedge1_ip }}/{{ storageedge1_cidr }}
routes:
- ip_netmask: NEED MANUAL CONVERSION: {''get_param'': ''StorageSupernet''}
next_hop: {{ storageedge1_gateway_ip }}
- type: vlan
vlan_id: {{ internal_apiedge1_vlan_id }}
addresses:
- ip_netmask: {{ internal_apiedge1_ip }}/{{ internal_apiedge1_cidr }}
routes:
- ip_netmask: NEED MANUAL CONVERSION: {''get_param'': ''InternalApiSupernet''}
next_hop: {{ internal_apiedge1_gateway_ip }}
- type: vlan
vlan_id: {{ tenantedge1_vlan_id }}
addresses:
- ip_netmask: {{ tenantedge1_ip }}/{{ tenantedge1_cidr }}
routes:
- ip_netmask: NEED MANUAL CONVERSION: {''get_param'': ''TenantSupernet''}
next_hop: {{ tenantedge1_gateway_ip }}

View File

@ -0,0 +1,78 @@
---
network_config:
- type: interface
name: nic1
mtu: {{ ctlplane_mtu }}
use_dhcp: false
dns_servers: {{ ctlplane_dns_nameservers }}
domain: {{ dns_search_domains }}
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
routes: {{ [ctlplane_host_routes] | flatten | unique }}
- type: interface
name: nic2
mtu: {{ storage_mtu }}
use_dhcp: false
- type: vlan
device: nic2
mtu: {{ storage_mtu }}
vlan_id: {{ storage_vlan_id }}
addresses:
- ip_netmask: {{ storage_ip }}/{{ storage_cidr }}
routes: {{ [storage_host_routes] | flatten | unique }}
- type: interface
name: nic5
mtu: {{ storage_mgmt_mtu }}
use_dhcp: false
- type: vlan
device: nic5
mtu: {{ storage_mgmt_mtu }}
vlan_id: {{ storage_mgmt_vlan_id }}
addresses:
- ip_netmask: {{ storage_mgmt_ip }}/{{ storage_mgmt_cidr }}
routes: {{ [storage_mgmt_host_routes] | flatten | unique }}
- type: interface
name: nic8
mtu: {{ internal_api_mtu }}
use_dhcp: false
- type: vlan
device: nic8
mtu: {{ internal_api_mtu }}
vlan_id: {{ internal_api_vlan_id }}
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
routes: {{ [internal_api_host_routes] | flatten | unique }}
- type: ovs_bridge
name: br-tenant
mtu: {{ tenant_mtu }}
dns_servers: {{ ctlplane_dns_nameservers }}
use_dhcp: false
members:
- type: interface
name: nic11
mtu: {{ tenant_mtu }}
use_dhcp: false
primary: true
- type: vlan
mtu: {{ tenant_mtu }}
vlan_id: {{ tenant_vlan_id }}
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}
routes: {{ [tenant_host_routes] | flatten | unique }}
- type: ovs_bridge
name: bridge_name
mtu: {{ external_mtu }}
dns_servers: {{ ctlplane_dns_nameservers }}
use_dhcp: false
members:
- type: interface
name: nic14
mtu: {{ external_mtu }}
use_dhcp: false
primary: true
- type: vlan
mtu: {{ external_mtu }}
vlan_id: {{ external_vlan_id }}
addresses:
- ip_netmask: {{ external_ip }}/{{ external_cidr }}
routes: {{ [external_host_routes, [{'default': True, 'next_hop': external_gateway_ip}]] | flatten | unique }}

View File

@ -0,0 +1,26 @@
---
network_config:
- type: interface
name: nic1
use_dhcp: false
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
- type: interface
name: nic2
use_dhcp: false
- type: vlan
device: nic2
vlan_id: {{ internal_api_vlan_id }}
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
- type: ovs_bridge
name: bridge_name
dns_servers: {{ ctlplane_dns_nameservers }}
members:
- type: interface
name: nic3
primary: true
- type: vlan
vlan_id: {{ tenant_vlan_id }}
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}

View File

@ -0,0 +1,56 @@
---
{% set mtu_list = [ctlplane_mtu] %}
{% for network in role_networks %}
{{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}
{%- endfor %}
{% set min_viable_mtu = mtu_list | max %}
network_config:
- type: linux_bridge
name: bridge_name
mtu: {{ min_viable_mtu }}
use_dhcp: false
dns_servers: {{ ctlplane_dns_nameservers }}
domain: {{ dns_search_domains }}
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
routes: {{ [ctlplane_host_routes] | flatten | unique }}
members:
- type: interface
name: interface_name
mtu: {{ min_viable_mtu }}
primary: true
- type: vlan
mtu: {{ storage_mtu }}
vlan_id: {{ storage_vlan_id }}
device: bridge_name
addresses:
- ip_netmask: {{ storage_ip }}/{{ storage_cidr }}
routes: {{ [storage_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ storage_mgmt_mtu }}
vlan_id: {{ storage_mgmt_vlan_id }}
device: bridge_name
addresses:
- ip_netmask: {{ storage_mgmt_ip }}/{{ storage_mgmt_cidr }}
routes: {{ [storage_mgmt_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ internal_api_mtu }}
vlan_id: {{ internal_api_vlan_id }}
device: bridge_name
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
routes: {{ [internal_api_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ tenant_mtu }}
vlan_id: {{ tenant_vlan_id }}
device: bridge_name
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}
routes: {{ [tenant_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ external_mtu }}
vlan_id: {{ external_vlan_id }}
device: bridge_name
addresses:
- ip_netmask: {{ external_ip }}/{{ external_cidr }}
routes: {{ [external_host_routes, [{'default': True, 'next_hop': external_gateway_ip}]] | flatten | unique }}

View File

@ -0,0 +1,51 @@
---
{% set mtu_list = [ctlplane_mtu] %}
{% for network in role_networks %}
{{ mtu_list.append(lookup('vars', networks_lower[network] ~ '_mtu')) }}
{%- endfor %}
{% set min_viable_mtu = mtu_list | max %}
network_config:
- type: ovs_bridge
name: bridge_name
mtu: {{ min_viable_mtu }}
use_dhcp: false
dns_servers: {{ ctlplane_dns_nameservers }}
domain: {{ dns_search_domains }}
addresses:
- ip_netmask: {{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}
routes: {{ [ctlplane_host_routes] | flatten | unique }}
members:
- type: interface
name: nic1
mtu: {{ min_viable_mtu }}
primary: true
- type: vlan
mtu: {{ storage_mtu }}
vlan_id: {{ storage_vlan_id }}
addresses:
- ip_netmask: {{ storage_ip }}/{{ storage_cidr }}
routes: {{ [storage_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ storage_mgmt_mtu }}
vlan_id: {{ storage_mgmt_vlan_id }}
addresses:
- ip_netmask: {{ storage_mgmt_ip }}/{{ storage_mgmt_cidr }}
routes: {{ [storage_mgmt_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ internal_api_mtu }}
vlan_id: {{ internal_api_vlan_id }}
addresses:
- ip_netmask: {{ internal_api_ip }}/{{ internal_api_cidr }}
routes: {{ [internal_api_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ tenant_mtu }}
vlan_id: {{ tenant_vlan_id }}
addresses:
- ip_netmask: {{ tenant_ip }}/{{ tenant_cidr }}
routes: {{ [tenant_host_routes] | flatten | unique }}
- type: vlan
mtu: {{ external_mtu }}
vlan_id: {{ external_vlan_id }}
addresses:
- ip_netmask: {{ external_ip }}/{{ external_cidr }}
routes: {{ [external_host_routes, [{'default': True, 'next_hop': external_gateway_ip}]] | flatten | unique }}

View File

@ -0,0 +1,32 @@
- name: Storage
name_lower: storage
- name: StorageEdge1
name_lower: storageedge1
- name: StorageEdge2
name_lower: storageedge2
- name: StorageMgmt
name_lower: storage_mgmt
- name: StorageMgmtEdge1
name_lower: storage_mgmtedge1
- name: StorageMgmtEdge2
name_lower: storage_mgmtedge2
- name: InternalApi
name_lower: internal_api
- name: InternalApiEdge1
name_lower: internal_apiedge1
- name: InternalApiEdge2
name_lower: internal_apiedge2
- name: Tenant
name_lower: tenant
- name: TenantEdge1
name_lower: tenantedge1
- name: TenantEdge2
name_lower: tenantedge2
- name: External
name_lower: external
- name: Management
name_lower: management

View File

@ -0,0 +1,4 @@
- name: InternalApi
name_lower: internal_api
- name: Tenant
name_lower: tenant

View File

@ -0,0 +1,17 @@
parameters: {}
resource_registry: {}
parameter_defaults:
StorageSupernet: 172.18.0.0/16
StorageMgmtSupernet: 172.19.0.0/16
InternalApiSupernet: 172.20.0.0/16
TenantSupernet: 172.21.0.0/16
ExternalSupernet: 172.200.0.0/16
ControlPlaneDefaultRoute: 172.16.0.200
ControlPlaneSubnetCidr: '24'
ControlPlaneEdge1DefaultRoute: 172.16.1.201
ControlPlaneEdge1SubnetCidr: '24'
ControlPlaneEdge2DefaultRoute: 172.16.2.202
ControlPlaneEdge2SubnetCidr: '24'
EC2MetadataIp: 172.16.0.200
Edge1EC2MetadataIp: 172.16.1.201
Edge2EC2MetadataIp: 172.16.2.202

View File

@ -0,0 +1,10 @@
parameters: {}
resource_registry: {}
parameter_defaults:
TestStringParameter: test_string
TestListParameter:
- test_list_element01
- test_list_element02
TestDictParameter:
test_key:
test_sub_key: test_value

View File

@ -0,0 +1,303 @@
#!/usr/bin/env python
# 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 os
import tempfile
import yaml
from oslotest import base
from tools import convert_heat_nic_config_to_ansible_j2 as converter
FAKE_PARAM_TO_VAR_MAP = {
'ControlPlaneDefaultRoute': '{{ ctlplane_gateway_ip }}',
'ControlPlaneIp': '{{ ctlplane_ip }}',
'ControlPlaneMtu': '{{ ctlplane_mtu }}',
'ControlPlaneStaticRoutes': '{{ ctlplane_host_routes }}',
'ControlPlaneSubnetCidr': '{{ ctlplane_subnet_cidr }}',
'DnsSearchDomains': '{{ dns_search_domains }}',
'DnsServers': '{{ ctlplane_dns_nameservers }}',
'InternalApiInterfaceDefaultRoute': '{{ internal_api_gateway_ip }}',
'InternalApiInterfaceRoutes': '{{ internal_api_host_routes }}',
'InternalApiIpSubnet': '{{ internal_api_ip }}/{{ internal_api_cidr }}',
'InternalApiMtu': '{{ internal_api_mtu }}',
'InternalApiNetworkVlanID': '{{ internal_api_vlan_id }}',
'NumDpdkInterfaceRxQueues': '{{ num_dpdk_interface_rx_queues }}',
'BondInterfaceOvsOptions': '{{ bond_interface_ovs_options }}',
'TenantInterfaceDefaultRoute': '{{ tenant_gateway_ip }}',
'TenantInterfaceRoutes': '{{ tenant_host_routes }}',
'TenantIpSubnet': '{{ tenant_ip }}/{{ tenant_cidr }}',
'TenantMtu': '{{ tenant_mtu }}',
'TenantNetworkVlanID': '{{ tenant_vlan_id }}',
}
class ConvertToAnsibleJ2TestCase(base.BaseTestCase):
def setUp(self):
super(ConvertToAnsibleJ2TestCase, self).setUp()
self.fake_param_to_var_map = FAKE_PARAM_TO_VAR_MAP
stack_env_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/stack_env_simple.yaml')
networks_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/networks_file_simple.yaml')
with open(stack_env_file, 'r') as f:
self.fake_stack_env = yaml.safe_load(f.read())
self.convert = converter.ConvertToAnsibleJ2(self.fake_stack_env,
networks_file)
def test_to_j2_var(self):
self.assertEqual('{{ some_var }}',
converter.ConvertToAnsibleJ2.to_j2_var('some_var'))
def test_to_j2_var_raises_on_unsupported_type(self):
for _type in [list(), dict(), int(), bool()]:
self.assertRaises(RuntimeError,
converter.ConvertToAnsibleJ2.to_j2_var,
_type)
def test_create_param_to_var_map(self):
networks_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/networks_file_simple.yaml')
param_to_var_map = self.convert.create_param_to_var_map(networks_file)
self.assertEqual(self.fake_param_to_var_map.keys(),
param_to_var_map.keys())
for key in param_to_var_map:
self.assertEqual(self.fake_param_to_var_map[key],
param_to_var_map[key])
def test_convert_get_param(self):
for k, v in self.fake_param_to_var_map.items():
old = {'get_param': k}
new = self.convert.convert_get_param(old)
self.assertEqual(v, new)
for k, v in self.fake_stack_env['parameter_defaults'].items():
old = {'get_param': k}
new = self.convert.convert_get_param(old)
if isinstance(v, str):
self.assertTrue(v in new)
self.assertTrue(new.startswith(converter.QUOTE_FIX + v))
elif isinstance(v, (bool, int, list, dict)):
self.assertEqual(v, new)
def test_convert_get_param_unable_param_not_defined(self):
old = {'get_param': 'UNKNOWN_PARAM'}
new = self.convert.convert_get_param(old)
self.assertEqual(converter.QUOTE_FIX
+ ('NEED MANUAL CONVERSION: {\'get_param\': '
'\'UNKNOWN_PARAM\'}') + converter.QUOTE_FIX, new)
def test_convert_get_param_comlex_data_struct_not_supported(self):
old = {'get_param': ['COMPLEX', 'DATA']}
new = self.convert.convert_get_param(old)
self.assertEqual(converter.QUOTE_FIX
+ ('NEED MANUAL CONVERSION: {\'get_param\': '
'[\'COMPLEX\', \'DATA\']}') + converter.QUOTE_FIX,
new)
def test_convert_get_attr(self):
old = {'get_attr': ['MinViableMtu', 'value']}
new = self.convert.convert_get_attr(old)
self.assertEqual('{{ min_viable_mtu }}', new)
def test_convert_get_attr_not_suported(self):
old = {'get_attr': ['UNSUPPORTED', 'value']}
new = self.convert.convert_get_attr(old)
self.assertEqual(('NEED MANUAL CONVERSION: '
'{\'get_attr\': [\'UNSUPPORTED\', \'value\']}'), new)
def test_convert_list_join(self):
list_join_attrs = ['/', ['foo', 'bar']]
new = self.convert.convert_list_join(list_join_attrs)
self.assertEqual('foo/bar', new)
def test_convert_list_concat(self):
list_concat_attrs = [['a_list', 'list_a'], ['list_b', 'b_list']]
new = self.convert.convert_list_concat(list_concat_attrs)
self.assertEqual(
("{{ [['%_fix_quote_%a_list%_fix_quote_%', "
"'%_fix_quote_%list_a%_fix_quote_%'], "
"['%_fix_quote_%list_b%_fix_quote_%', "
"'%_fix_quote_%b_list%_fix_quote_%']] "
"| flatten }}"), new)
def test_convert_list_concat_raise_if_not_list(self):
list_concat_attrs = 'NOT_A_LIST'
self.assertRaises(RuntimeError,
self.convert.convert_list_concat,
list_concat_attrs)
def test_convert_list_concat_unique(self):
convert_list_concat_unique = [['a_list', 'b_list'],
['list_b', 'b_list']]
new = self.convert.convert_list_concat_unique(
convert_list_concat_unique)
self.assertEqual(
("{{ [['%_fix_quote_%a_list%_fix_quote_%', "
"'%_fix_quote_%b_list%_fix_quote_%'], "
"['%_fix_quote_%list_b%_fix_quote_%', "
"'%_fix_quote_%b_list%_fix_quote_%']] "
"| flatten | unique }}"), new)
def test_convert_list_concat_unique_raise_if_not_list(self):
convert_list_concat_unique = 'NOT_A_LIST'
self.assertRaises(RuntimeError,
self.convert.convert_list_concat_unique,
convert_list_concat_unique)
def test_recursive_convert_str_bool_int_not_converted(self):
# old is a string
self.assertEqual('string', self.convert.recursive_convert('string'))
# old is a boolean
self.assertEqual(True, self.convert.recursive_convert(True))
# old is a number
self.assertEqual(1, self.convert.recursive_convert(1))
def test_recursive_convert_nothing_to_convert(self):
old = {'foo': 'bar', 'baz': ['a', 'b', {'c': 'd'}]}
self.assertEqual(old, self.convert.recursive_convert(old))
def test_recursive_convert_complex(self):
addresses = [
{'ip_netmask': {
'list_join': ['/',
[{'get_param': 'ControlPlaneIp'},
{'get_param': 'ControlPlaneSubnetCidr'}]
]}}]
old = {'type': 'interface', 'name': 'nic1', 'use_dhcp': 'false',
'addresses': addresses}
expected = {'name': 'nic1', 'type': 'interface', 'use_dhcp': 'false',
'addresses': [
{'ip_netmask':
'{{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}'}]}
self.assertEqual(expected, self.convert.recursive_convert(old))
def test_recursive_convert_unsupported_intrinsic_fn(self):
for fn in converter.UNSUPPORTED_HEAT_INTRINSIC_FUNCTIONS:
old = {'foo': 'bar', 'baz': ['a', 'b', {fn: 'd'}]}
unsupported_str = (
"UNSUPPORTED HEAT INTRINSIC FUNCTION {x} REQUIRES MANUAL "
"CONVERSION {{'{x}': 'd'}}".format(x=fn))
new = {'foo': 'bar', 'baz': ['a', 'b', unsupported_str]}
self.assertEqual(new, self.convert.recursive_convert(old))
def test_convert_template(self):
template_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/heat_templates/simple.yaml')
j2_config, mtu_header, j2_header = self.convert.convert_template(
template_file)
self.assertIsNone(mtu_header)
self.assertIsNone(j2_header)
expected = [
{'name': 'nic1',
'type': 'interface',
'use_dhcp': False,
'addresses': [{
'ip_netmask': '{{ ctlplane_ip }}/{{ ctlplane_subnet_cidr }}'}
]},
{'name': 'nic2',
'type': 'interface',
'use_dhcp': False},
{'type': 'vlan',
'device': 'nic2',
'vlan_id': '{{ internal_api_vlan_id }}',
'addresses': [{
'ip_netmask': '{{ internal_api_ip }}/{{ internal_api_cidr }}'}
]},
{'type': 'ovs_bridge',
'name': 'bridge_name',
'dns_servers': '{{ ctlplane_dns_nameservers }}',
'members': [
{'type': 'interface',
'name': 'nic3',
'primary': True},
{'type': 'vlan',
'vlan_id': '{{ tenant_vlan_id }}',
'addresses': [{
'ip_netmask': '{{ tenant_ip }}/{{ tenant_cidr }}'}
]},
]},
]
self.assertEqual(expected, j2_config['network_config'])
def convert_heat_to_ansible_j2(self, heat_template, j2_reference,
networks_file='network_file_complex.yaml',
stack_env='stack_env_simple.yaml'):
networks_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/' + networks_file)
template_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/heat_templates/' + heat_template)
reference_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/j2_references/' + j2_reference)
stack_env_file = os.path.join(
os.path.dirname(__file__),
'nic_config_convert_samples/' + stack_env)
with open(stack_env_file, 'r') as f:
stack_env = yaml.safe_load(f.read())
convert = converter.ConvertToAnsibleJ2(stack_env, networks_file)
j2_config, mtu_header, j2_header = convert.convert_template(
template_file)
with tempfile.TemporaryDirectory() as temp_dir:
j2_template = os.path.abspath(temp_dir) + '/j2_template.j2'
converter.write_j2_template(j2_template, j2_config, mtu_header,
j2_header)
with open(reference_file, 'r') as a:
with open(j2_template, 'r') as b:
reference = a.read()
result = b.read()
self.assertEqual(reference, result)
def test_convert_and_write_file_simple(self):
self.convert_heat_to_ansible_j2(
'simple.yaml', 'simple.j2',
networks_file='networks_file_simple.yaml')
def test_convert_and_write_file_complex01_incomplete(self):
self.convert_heat_to_ansible_j2('complex.yaml',
'complex_incomplete.j2')
def test_convert_and_write_file_complex01_complete(self):
self.convert_heat_to_ansible_j2('complex.yaml', 'complex_complete.j2',
stack_env='stack_env_complex.yaml')
def test_convert_2_linux_bonds_vlan_controller(self):
self.convert_heat_to_ansible_j2('2-linux-bonds-vlans-controller.yaml',
'2-linux-bonds-vlans-controller.j2')
def test_convert_bond_vlans_controller(self):
self.convert_heat_to_ansible_j2('bond-vlans-controller.yaml',
'bond-vlans-controller.j2')
def test_convert_multiple_nics_vlans_controller(self):
self.convert_heat_to_ansible_j2('multiple-nics-vlans-controller.yaml',
'multiple-nics-vlans-controller.j2')
def test_convert_single_nic_linux_bridge_vlans_controller(self):
self.convert_heat_to_ansible_j2(
'single-nic-linux-bridge-vlans-controller.yaml',
'single-nic-linux-bridge-vlans-controller.j2')
def test_convert_single_nic_vlans_controller(self):
self.convert_heat_to_ansible_j2('single-nic-vlans-controller.yaml',
'single-nic-vlans-controller.j2')

View File

@ -1371,6 +1371,9 @@ for base_path in path_args:
dirs.remove('.tox')
for f in files:
file_path = os.path.join(subdir, f)
if 'tools/tests/nic_config_convert_samples' in file_path:
continue
if 'environments/services-docker' in file_path:
print("ERROR: environments/services-docker should not be "
"used any more, use environments/services instead: "