Merge "Wire-in net_config_override"
This commit is contained in:
commit
c8009d81a2
@ -13,7 +13,11 @@
|
||||
# under the License.
|
||||
#
|
||||
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
|
||||
from jinja2 import Template
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
@ -110,6 +114,163 @@ class TestUndercloudInstall(TestPluginV1):
|
||||
'-e', '/foo/undercloud_parameters.yaml',
|
||||
'--log-file=/tmp/install-undercloud.log'])
|
||||
|
||||
@mock.patch('os.mkdir')
|
||||
@mock.patch('tripleoclient.utils.write_env_file', autospec=True)
|
||||
@mock.patch('tripleoclient.v1.undercloud_config.'
|
||||
'_generate_masquerade_networks', autospec=True)
|
||||
@mock.patch('tripleoclient.v1.undercloud_config.'
|
||||
'_generate_subnets_static_routes', autospec=True)
|
||||
@mock.patch('tripleoclient.v1.undercloud_config.'
|
||||
'_get_jinja_env_source', autospec=True)
|
||||
@mock.patch('tripleoclient.v1.undercloud_config.'
|
||||
'_get_unknown_instack_tags', return_value=None, autospec=True)
|
||||
@mock.patch('jinja2.meta.find_undeclared_variables', return_value={},
|
||||
autospec=True)
|
||||
@mock.patch('os.getcwd', return_value='/tmp')
|
||||
@mock.patch('subprocess.check_call', autospec=True)
|
||||
def test_undercloud_install_with_heat_net_conf_over(self, mock_subprocess,
|
||||
mock_cwd, mock_j2_meta,
|
||||
mock_get_unknown_tags,
|
||||
mock_get_j2,
|
||||
mock_sroutes,
|
||||
mock_masq,
|
||||
mock_wr, mock_os):
|
||||
self.conf.config(net_config_override='/foo/net-config.json')
|
||||
self.conf.config(local_interface='ethX')
|
||||
self.conf.config(undercloud_public_host='4.3.2.1')
|
||||
self.conf.config(local_mtu='1234')
|
||||
self.conf.config(undercloud_nameservers=['8.8.8.8', '8.8.4.4'])
|
||||
self.conf.config(subnets='foo')
|
||||
mock_masq.return_value = {'1.1.1.1/11': ['2.2.2.2/22']}
|
||||
mock_sroutes.return_value = {'ip_netmask': '1.1.1.1/11',
|
||||
'next_hop': '1.1.1.1'}
|
||||
instack_net_conf = """
|
||||
"network_config": [
|
||||
{
|
||||
"type": "ovs_bridge",
|
||||
"name": "br-ctlplane",
|
||||
"ovs_extra": [
|
||||
"br-set-external-id br-ctlplane bridge-id br-ctlplane"
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "{{LOCAL_INTERFACE}}",
|
||||
"primary": "true",
|
||||
"mtu": {{LOCAL_MTU}},
|
||||
"dns_servers": {{UNDERCLOUD_NAMESERVERS}}
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"ip_netmask": "{{PUBLIC_INTERFACE_IP}}"
|
||||
}
|
||||
],
|
||||
"routes": {{SUBNETS_STATIC_ROUTES}},
|
||||
"mtu": {{LOCAL_MTU}}
|
||||
}
|
||||
]
|
||||
"""
|
||||
expected_net_conf = json.loads(
|
||||
"""
|
||||
{"network_config": [
|
||||
{
|
||||
"type": "ovs_bridge",
|
||||
"name": "br-ctlplane",
|
||||
"ovs_extra": [
|
||||
"br-set-external-id br-ctlplane bridge-id br-ctlplane"
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "ethX",
|
||||
"primary": "true",
|
||||
"mtu": 1234,
|
||||
"dns_servers": ["8.8.8.8", "8.8.4.4"]
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"ip_netmask": "4.3.2.1"
|
||||
}
|
||||
],
|
||||
"routes": {"next_hop": "1.1.1.1", "ip_netmask": "1.1.1.1/11"},
|
||||
"mtu": 1234
|
||||
}
|
||||
]}
|
||||
"""
|
||||
)
|
||||
env = mock.Mock()
|
||||
env.get_template = mock.Mock(return_value=Template(instack_net_conf))
|
||||
mock_get_j2.return_value = (env, None)
|
||||
arglist = ['--use-heat', '--no-validations']
|
||||
verifylist = []
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
os_orig = os.path.exists
|
||||
with mock.patch('os.path.exists') as mock_exists:
|
||||
|
||||
def fcheck(*args, **kwargs):
|
||||
if '/foo/net-config.json' in args:
|
||||
return True
|
||||
return os_orig(*args, **kwargs)
|
||||
|
||||
mock_exists.side_effect = fcheck
|
||||
self.cmd.take_action(parsed_args)
|
||||
|
||||
# unpack the write env file call to verify if the produced net config
|
||||
# override JSON matches our expectations
|
||||
found_net_conf_override = False
|
||||
for call in mock_wr.call_args_list:
|
||||
args, kwargs = call
|
||||
for a in args:
|
||||
if 'UndercloudNetConfigOverride' in a:
|
||||
found_net_conf_override = True
|
||||
self.assertTrue(
|
||||
a['UndercloudNetConfigOverride'] == expected_net_conf)
|
||||
self.assertTrue(found_net_conf_override)
|
||||
|
||||
mock_subprocess.assert_called_with(
|
||||
['sudo', 'openstack', 'tripleo', 'deploy', '--standalone',
|
||||
'--local-domain=localdomain',
|
||||
'--local-ip=192.168.24.1/24',
|
||||
'--templates=/usr/share/openstack-tripleo-heat-templates/',
|
||||
'--heat-native', '-e', '/home/stack/foo.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/'
|
||||
'environments/services/masquerade-networks.yaml',
|
||||
'-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/ironic.yaml',
|
||||
'-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/ironic-inspector.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/mistral.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/zaqar.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/tripleo-ui.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/tempest.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'public-tls-undercloud.yaml',
|
||||
'--public-virtual-ip', '4.3.2.1',
|
||||
'--control-virtual-ip', '192.168.24.3', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'ssl/tls-endpoints-public-ip.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'use-dns-for-vips.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/undercloud-haproxy.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'services/undercloud-keepalived.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'docker.yaml', '-e',
|
||||
'/usr/share/openstack-tripleo-heat-templates/environments/'
|
||||
'undercloud.yaml', '--output-dir=/home/stack',
|
||||
'-e', '/home/stack/undercloud_parameters.yaml',
|
||||
'--log-file=/tmp/install-undercloud.log'])
|
||||
|
||||
@mock.patch('os.mkdir')
|
||||
@mock.patch('tripleoclient.utils.write_env_file', autospec=True)
|
||||
@mock.patch('os.getcwd', return_value='/tmp')
|
||||
|
@ -16,6 +16,8 @@
|
||||
"""Plugin action implementation"""
|
||||
|
||||
import copy
|
||||
import jinja2
|
||||
import json
|
||||
import logging
|
||||
import netaddr
|
||||
import os
|
||||
@ -29,10 +31,51 @@ from cryptography.hazmat.primitives import serialization
|
||||
from oslo_config import cfg
|
||||
from tripleo_common.image import kolla_builder
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import exceptions
|
||||
from tripleoclient import utils
|
||||
|
||||
from tripleoclient.v1 import undercloud_preflight
|
||||
|
||||
NETCONFIG_TAGS_EXAMPLE = """
|
||||
"network_config": [
|
||||
{
|
||||
"type": "ovs_bridge",
|
||||
"name": "br-ctlplane",
|
||||
"ovs_extra": [
|
||||
"br-set-external-id br-ctlplane bridge-id br-ctlplane"
|
||||
],
|
||||
"members": [
|
||||
{
|
||||
"type": "interface",
|
||||
"name": "{{LOCAL_INTERFACE}}",
|
||||
"primary": "true",
|
||||
"mtu": {{LOCAL_MTU}},
|
||||
"dns_servers": {{UNDERCLOUD_NAMESERVERS}}
|
||||
}
|
||||
],
|
||||
"addresses": [
|
||||
{
|
||||
"ip_netmask": "{{PUBLIC_INTERFACE_IP}}"
|
||||
}
|
||||
],
|
||||
"routes": {{SUBNETS_STATIC_ROUTES}},
|
||||
"mtu": {{LOCAL_MTU}}
|
||||
}
|
||||
]
|
||||
"""
|
||||
|
||||
# Provides mappings for some of the instack_env tags to undercloud heat
|
||||
# params or undercloud.conf opts known here (as a fallback), needed to maintain
|
||||
# feature parity with instack net config override templates.
|
||||
# TODO(bogdando): all of the needed mappings should be wired-in, eventually
|
||||
INSTACK_NETCONF_MAPPING = {
|
||||
'LOCAL_INTERFACE': 'local_interface',
|
||||
'LOCAL_IP': 'local_ip',
|
||||
'LOCAL_MTU': 'UndercloudLocalMtu',
|
||||
'PUBLIC_INTERFACE_IP': 'undercloud_public_host', # can't be 'CloudName'
|
||||
'UNDERCLOUD_NAMESERVERS': 'undercloud_nameservers',
|
||||
'SUBNETS_STATIC_ROUTES': 'ControlPlaneStaticRoutes',
|
||||
}
|
||||
|
||||
PARAMETER_MAPPING = {
|
||||
'inspection_interface': 'IronicInspectorInterface',
|
||||
@ -237,11 +280,18 @@ _opts = [
|
||||
),
|
||||
cfg.StrOpt('net_config_override',
|
||||
default='',
|
||||
help=('Path to network config override template. If set, this '
|
||||
'template will be used to configure the networking via '
|
||||
'os-net-config. Must be in json format. '
|
||||
'If you wish to disable os-net-config you can set this'
|
||||
'location to an empty file.')
|
||||
help=('Path to network config override template. Relative '
|
||||
'paths get computed inside of the given heat templates '
|
||||
'directory. Must be in json format. '
|
||||
'Its content overrides anything in t-h-t '
|
||||
'UndercloudNetConfigOverride. The processed template '
|
||||
'is then passed in Heat via the top scope '
|
||||
'undercloud_parameters.yaml file created in '
|
||||
'output_dir and used to configure the networking '
|
||||
'via run-os-net-config. If you wish to disable '
|
||||
'you can set this location to an empty file.'
|
||||
'Templated for instack j2 tags '
|
||||
'may be used, for example:\n%s ') % NETCONFIG_TAGS_EXAMPLE
|
||||
),
|
||||
cfg.StrOpt('inspection_interface',
|
||||
default='br-ctlplane',
|
||||
@ -462,6 +512,22 @@ def _load_config():
|
||||
CONF(conf_params)
|
||||
|
||||
|
||||
def _get_jinja_env_source(f):
|
||||
path, filename = os.path.split(f)
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(path))
|
||||
src = env.loader.get_source(env, filename)[0]
|
||||
return (env, src)
|
||||
|
||||
|
||||
def _get_unknown_instack_tags(env, src):
|
||||
found_tags = set(jinja2.meta.find_undeclared_variables(env.parse(src)))
|
||||
known_tags = set(INSTACK_NETCONF_MAPPING.keys())
|
||||
if found_tags <= known_tags:
|
||||
return (', ').join(found_tags - known_tags)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _process_drivers_and_hardware_types(conf, env):
|
||||
"""Populate the environment with ironic driver information."""
|
||||
# Ensure correct rendering of the list and uniqueness of the items
|
||||
@ -783,9 +849,6 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False,
|
||||
if not os.path.isdir(CONF['output_dir']):
|
||||
os.mkdir(CONF['output_dir'])
|
||||
|
||||
deploy_args += ['-e', params_file]
|
||||
utils.write_env_file(env_data, params_file, registry_overwrites)
|
||||
|
||||
if CONF.get('cleanup'):
|
||||
deploy_args.append('--cleanup')
|
||||
|
||||
@ -793,6 +856,61 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False,
|
||||
for custom_file in CONF['custom_env_files']:
|
||||
deploy_args += ['-e', custom_file]
|
||||
|
||||
if CONF.get('net_config_override', None):
|
||||
data_file = CONF['net_config_override']
|
||||
if os.path.abspath(data_file) != data_file:
|
||||
data_file = os.path.join(tht_templates, data_file)
|
||||
|
||||
if not os.path.exists(data_file):
|
||||
msg = "Could not find net_config_override file '%s'" % data_file
|
||||
LOG.error(msg)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
# NOTE(bogdando): Process templated net config override data:
|
||||
# * get a list of used instack_env j2 tags (j2 vars, like {{foo}}),
|
||||
# * fetch values for the tags from the known mappins,
|
||||
# * raise, if there is unmatched tags left
|
||||
# * render the template into a JSON dict
|
||||
net_config_env, template_source = _get_jinja_env_source(data_file)
|
||||
unknown_tags = _get_unknown_instack_tags(net_config_env,
|
||||
template_source)
|
||||
if unknown_tags:
|
||||
msg = ('Can not render net_config_override file %s contains '
|
||||
'unknown instack_env j2 tags: %s' % (
|
||||
data_file, unknown_tags))
|
||||
LOG.error(msg)
|
||||
raise exceptions.DeploymentError(msg)
|
||||
|
||||
# Create rendering context from the known to be present mappings for
|
||||
# identified instack_env tags to generated in env_data undercloud heat
|
||||
# params. Fall back to config opts, when env_data misses a param.
|
||||
context = {}
|
||||
for tag in INSTACK_NETCONF_MAPPING.keys():
|
||||
mapped_value = INSTACK_NETCONF_MAPPING[tag]
|
||||
if mapped_value in env_data.keys() or mapped_value in CONF.keys():
|
||||
context[tag] = env_data.get(
|
||||
mapped_value, None) or CONF[mapped_value]
|
||||
|
||||
# this returns a unicode string, convert it in into json
|
||||
net_config_str = net_config_env.get_template(
|
||||
os.path.split(data_file)[-1]).render(context).replace(
|
||||
"'", '"').replace('"', '"')
|
||||
try:
|
||||
net_config_json = json.loads(net_config_str)
|
||||
except ValueError:
|
||||
net_config_json = json.loads("{%s}" % net_config_str)
|
||||
|
||||
if 'network_config' not in net_config_json:
|
||||
msg = ('Unsupported data format in net_config_override '
|
||||
'file %s: %s' % (data_file, net_config_str))
|
||||
LOG(msg)
|
||||
raise exceptions.DeploymentError(msg)
|
||||
|
||||
env_data['UndercloudNetConfigOverride'] = net_config_json
|
||||
|
||||
deploy_args += ['-e', params_file]
|
||||
utils.write_env_file(env_data, params_file, registry_overwrites)
|
||||
|
||||
if CONF.get('hieradata_override', None):
|
||||
data_file = CONF['hieradata_override']
|
||||
if os.path.abspath(data_file) != data_file:
|
||||
|
Loading…
Reference in New Issue
Block a user