baremetal node export - auto convert Heat nic-conf
If no NetworkConfigTemplate parameter is set when exporting overcloud nodes to baremetal_deployment.yaml an attempt to look for a legacy Heat nic-config template is made. In case a heat nic-config template is found it will be downloaded from the stack to a temporary file. The tool $THT/tools/convert_heat_nic_config_to_ansible_j2.py is then used to convert the template to an ansible j2 nic-config template. The template created by the conversion tool is placed in the working-dir and the baremetal_deployment.yaml network_config -> template parameter will point to the converted template. Change-Id: Idbb2345c2b94370bbdc455a96869fcf85c5edfc7
This commit is contained in:
parent
eb0f82b98d
commit
f965bc4f54
|
@ -1010,6 +1010,8 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
{'name': 'Compute'}
|
{'name': 'Compute'}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
networks_data = []
|
||||||
|
|
||||||
self.stack_dict = {
|
self.stack_dict = {
|
||||||
'parameters': {
|
'parameters': {
|
||||||
'ComputeHostnameFormat': '%stackname%-novacompute-%index%',
|
'ComputeHostnameFormat': '%stackname%-novacompute-%index%',
|
||||||
|
@ -1020,7 +1022,7 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
'output_key': 'TripleoHeatTemplatesJinja2RenderingDataSources',
|
'output_key': 'TripleoHeatTemplatesJinja2RenderingDataSources',
|
||||||
'output_value': {
|
'output_value': {
|
||||||
'roles_data': roles_data,
|
'roles_data': roles_data,
|
||||||
'networks_data': {}
|
'networks_data': networks_data,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'output_key': 'AnsibleHostVarsMap',
|
'output_key': 'AnsibleHostVarsMap',
|
||||||
|
@ -1165,8 +1167,17 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
mode='w', delete=False, suffix='.yaml')
|
mode='w', delete=False, suffix='.yaml')
|
||||||
self.roles_file.write(yaml.safe_dump(roles_data))
|
self.roles_file.write(yaml.safe_dump(roles_data))
|
||||||
self.roles_file.close()
|
self.roles_file.close()
|
||||||
|
|
||||||
|
self.networks_file = tempfile.NamedTemporaryFile(
|
||||||
|
mode='w', delete=False, suffix='.yaml')
|
||||||
|
self.networks_file.write(yaml.safe_dump(networks_data))
|
||||||
|
self.networks_file.close()
|
||||||
|
|
||||||
|
self.working_dir = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
self.addCleanup(os.unlink, self.extract_file.name)
|
self.addCleanup(os.unlink, self.extract_file.name)
|
||||||
self.addCleanup(os.unlink, self.roles_file.name)
|
self.addCleanup(os.unlink, self.roles_file.name)
|
||||||
|
self.addCleanup(os.unlink, self.networks_file.name)
|
||||||
|
|
||||||
def test_extract(self):
|
def test_extract(self):
|
||||||
stack = mock.Mock()
|
stack = mock.Mock()
|
||||||
|
@ -1259,10 +1270,12 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
|
|
||||||
self.baremetal.node.list.return_value = self.nodes
|
self.baremetal.node.list.return_value = self.nodes
|
||||||
argslist = ['--roles-file', self.roles_file.name,
|
argslist = ['--roles-file', self.roles_file.name,
|
||||||
|
'--networks-file', self.networks_file.name,
|
||||||
'--output', self.extract_file.name,
|
'--output', self.extract_file.name,
|
||||||
'--yes']
|
'--yes']
|
||||||
self.app.command_options = argslist
|
self.app.command_options = argslist
|
||||||
verifylist = [('roles_file', self.roles_file.name),
|
verifylist = [('roles_file', self.roles_file.name),
|
||||||
|
('networks_file', self.networks_file.name),
|
||||||
('output', self.extract_file.name),
|
('output', self.extract_file.name),
|
||||||
('yes', True)]
|
('yes', True)]
|
||||||
|
|
||||||
|
@ -1358,6 +1371,114 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
with open(self.extract_file.name) as f:
|
with open(self.extract_file.name) as f:
|
||||||
self.assertEqual(yaml.safe_load(result), yaml.safe_load(f))
|
self.assertEqual(yaml.safe_load(result), yaml.safe_load(f))
|
||||||
|
|
||||||
|
@mock.patch('tripleoclient.utils.run_command_and_log', autospec=True)
|
||||||
|
def test_extract_convert_nic_configs(self, mock_run_cmd):
|
||||||
|
stack = mock.Mock()
|
||||||
|
stack.stack_name = 'overcloud'
|
||||||
|
stack.to_dict.return_value = self.stack_dict
|
||||||
|
stack.files.return_value = {
|
||||||
|
'https://1.1.1.1:13808/v1/AUTH_xx/overcloud/user-files/'
|
||||||
|
'home/stack/overcloud/compute-net-config.yaml': 'FAKE_CONTENT'}
|
||||||
|
stack.environment.return_value = {
|
||||||
|
'resource_registry': {
|
||||||
|
'OS::TripleO::Compute::Net::SoftwareConfig':
|
||||||
|
'https://1.1.1.1:13808/v1/AUTH_xx/overcloud/user-files/'
|
||||||
|
'home/stack/overcloud/compute-net-config.yaml'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.orchestration.stacks.get.return_value = stack
|
||||||
|
|
||||||
|
self.baremetal.node.list.return_value = self.nodes
|
||||||
|
|
||||||
|
mock_run_cmd.return_value = 0
|
||||||
|
|
||||||
|
argslist = ['--output', self.extract_file.name,
|
||||||
|
'--working-dir', self.working_dir.name,
|
||||||
|
'--yes']
|
||||||
|
self.app.command_options = argslist
|
||||||
|
verifylist = [('output', self.extract_file.name),
|
||||||
|
('working_dir', self.working_dir.name),
|
||||||
|
('yes', True)]
|
||||||
|
parsed_args = self.check_parser(self.cmd,
|
||||||
|
argslist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
result = self.cmd.app.stdout.make_string()
|
||||||
|
heat_nic_conf_path = os.path.join(self.working_dir.name,
|
||||||
|
'nic-configs',
|
||||||
|
'compute-net-config.yaml')
|
||||||
|
cmd = ['/usr/share/openstack-tripleo-heat-templates/tools/'
|
||||||
|
'convert_heat_nic_config_to_ansible_j2.py',
|
||||||
|
'--yes',
|
||||||
|
'--stack', stack.stack_name,
|
||||||
|
'--networks_file', mock.ANY,
|
||||||
|
heat_nic_conf_path]
|
||||||
|
mock_run_cmd.assert_called_once_with(mock.ANY, cmd)
|
||||||
|
self.assertEqual([{
|
||||||
|
'name': 'Compute',
|
||||||
|
'count': 1,
|
||||||
|
'hostname_format': '%stackname%-novacompute-%index%',
|
||||||
|
'defaults': {
|
||||||
|
'network_config': {'network_config_update': False,
|
||||||
|
'physical_bridge_name': 'br-ex',
|
||||||
|
'public_interface_name': 'nic1',
|
||||||
|
'template':
|
||||||
|
os.path.join(self.working_dir.name,
|
||||||
|
'nic-configs',
|
||||||
|
'compute-net-config.j2')},
|
||||||
|
'networks': [{'network': 'ctlplane',
|
||||||
|
'vif': True},
|
||||||
|
{'network': 'internal_api',
|
||||||
|
'subnet': 'internal_api_b'}]
|
||||||
|
},
|
||||||
|
'instances': [{
|
||||||
|
'hostname': 'overcloud-novacompute-0',
|
||||||
|
'resource_class': 'compute',
|
||||||
|
'name': 'bm-3-uuid'
|
||||||
|
}],
|
||||||
|
}, {
|
||||||
|
'name': 'Controller',
|
||||||
|
'count': 3,
|
||||||
|
'hostname_format': '%stackname%-controller-%index%',
|
||||||
|
'defaults': {
|
||||||
|
'network_config': {'default_route_network': ['External'],
|
||||||
|
'network_config_update': False,
|
||||||
|
'networks_skip_config': ['Tenant'],
|
||||||
|
'physical_bridge_name': 'br-ex',
|
||||||
|
'public_interface_name': 'nic1',
|
||||||
|
'template': 'templates/controller.j2'},
|
||||||
|
'networks': [{'network': 'ctlplane',
|
||||||
|
'vif': True},
|
||||||
|
{'network': 'external',
|
||||||
|
'subnet': 'external_a'},
|
||||||
|
{'network': 'internal_api',
|
||||||
|
'subnet': 'internal_api_a'}]
|
||||||
|
},
|
||||||
|
'instances': [{
|
||||||
|
'hostname': 'overcloud-controller-0',
|
||||||
|
'resource_class': 'controller',
|
||||||
|
'name': 'bm-0-uuid'
|
||||||
|
}, {
|
||||||
|
'hostname': 'overcloud-controller-1',
|
||||||
|
'resource_class': 'controller',
|
||||||
|
'name': 'bm-1-uuid'
|
||||||
|
}, {
|
||||||
|
'hostname': 'overcloud-controller-2',
|
||||||
|
'name': 'bm-2-uuid'
|
||||||
|
}],
|
||||||
|
}], yaml.safe_load(result))
|
||||||
|
|
||||||
|
with open(self.extract_file.name) as f:
|
||||||
|
file_content = f.read()
|
||||||
|
self.assertEqual(yaml.safe_load(result), yaml.safe_load(file_content))
|
||||||
|
self.assertIn('WARNING: Network config for role Compute was '
|
||||||
|
'automatically converted from Heat template to Ansible '
|
||||||
|
'Jinja2 template. Please review the file: {}\n'
|
||||||
|
.format(os.path.join(self.working_dir.name,
|
||||||
|
'nic-configs',
|
||||||
|
'compute-net-config.j2')),
|
||||||
|
file_content)
|
||||||
|
|
||||||
def test_extract_empty(self):
|
def test_extract_empty(self):
|
||||||
stack_dict = {
|
stack_dict = {
|
||||||
'parameters': {},
|
'parameters': {},
|
||||||
|
@ -1371,9 +1492,11 @@ class TestExtractProvisionedNode(test_utils.TestCommand):
|
||||||
|
|
||||||
self.baremetal.node.list.return_value = nodes
|
self.baremetal.node.list.return_value = nodes
|
||||||
|
|
||||||
argslist = ['--roles-file', self.roles_file.name]
|
argslist = ['--roles-file', self.roles_file.name,
|
||||||
|
'--networks-file', self.networks_file.name]
|
||||||
self.app.command_options = argslist
|
self.app.command_options = argslist
|
||||||
verifylist = [('roles_file', self.roles_file.name)]
|
verifylist = [('roles_file', self.roles_file.name),
|
||||||
|
('networks_file', self.networks_file.name)]
|
||||||
|
|
||||||
parsed_args = self.check_parser(self.cmd,
|
parsed_args = self.check_parser(self.cmd,
|
||||||
argslist, verifylist)
|
argslist, verifylist)
|
||||||
|
|
|
@ -29,6 +29,7 @@ from openstack import exceptions as openstack_exc
|
||||||
from osc_lib import exceptions as oscexc
|
from osc_lib import exceptions as oscexc
|
||||||
from osc_lib.i18n import _
|
from osc_lib.i18n import _
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
|
import tempfile
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from tripleoclient import command
|
from tripleoclient import command
|
||||||
|
@ -519,6 +520,15 @@ class ExtractProvisionedNode(command.Command):
|
||||||
parser.add_argument('--roles-file', '-r', dest='roles_file',
|
parser.add_argument('--roles-file', '-r', dest='roles_file',
|
||||||
required=False,
|
required=False,
|
||||||
help=_('Role data definition file'))
|
help=_('Role data definition file'))
|
||||||
|
parser.add_argument('--networks-file', '-n', dest='networks_file',
|
||||||
|
required=False,
|
||||||
|
help=_('Network data definition file'))
|
||||||
|
parser.add_argument('--working-dir',
|
||||||
|
action='store',
|
||||||
|
help=_('The working directory for the deployment '
|
||||||
|
'where all input, output, and generated '
|
||||||
|
'files will be stored.\nDefaults to '
|
||||||
|
'"$HOME/overcloud-deploy/<stack>"'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def _get_subnet_from_net_name_and_ip(self, net_name, ip_addr):
|
def _get_subnet_from_net_name_and_ip(self, net_name, ip_addr):
|
||||||
|
@ -545,9 +555,61 @@ class ExtractProvisionedNode(command.Command):
|
||||||
"network %(net)s." % {'ip': ip_addr,
|
"network %(net)s." % {'ip': ip_addr,
|
||||||
'net': net_name})
|
'net': net_name})
|
||||||
|
|
||||||
|
def _convert_heat_nic_conf_to_j2(self, stack, role_name, network_data,
|
||||||
|
resource_registry, parsed_args):
|
||||||
|
heat_nic_conf = resource_registry.get(
|
||||||
|
'OS::TripleO::{}::Net::SoftwareConfig'.format(role_name))
|
||||||
|
if heat_nic_conf is None or heat_nic_conf == 'OS::Heat::None':
|
||||||
|
return None
|
||||||
|
|
||||||
|
j2_nic_conf_dir = os.path.join(self.working_dir, 'nic-configs')
|
||||||
|
oooutils.makedirs(j2_nic_conf_dir)
|
||||||
|
|
||||||
|
heat_nic_conf_basename = os.path.basename(heat_nic_conf)
|
||||||
|
tmp_heat_nic_conf_path = os.path.join(j2_nic_conf_dir,
|
||||||
|
heat_nic_conf_basename)
|
||||||
|
heat_nic_conf_content = stack.files().get(heat_nic_conf)
|
||||||
|
|
||||||
|
j2_nic_conf_basename = (heat_nic_conf_basename.rstrip('.yaml') + '.j2')
|
||||||
|
j2_nic_conf_path = os.path.join(j2_nic_conf_dir, j2_nic_conf_basename)
|
||||||
|
|
||||||
|
tmp_net_data_fd, tmp_net_data_path = tempfile.mkstemp(suffix=".yaml")
|
||||||
|
try:
|
||||||
|
with open(tmp_net_data_path, 'w') as tmp_net_data:
|
||||||
|
tmp_net_data.write(yaml.safe_dump(network_data))
|
||||||
|
with open(tmp_heat_nic_conf_path, 'w') as tmp_heat_nic_conf:
|
||||||
|
tmp_heat_nic_conf.write(heat_nic_conf_content)
|
||||||
|
|
||||||
|
cmd = ['/usr/share/openstack-tripleo-heat-templates/tools/'
|
||||||
|
'convert_heat_nic_config_to_ansible_j2.py']
|
||||||
|
if parsed_args.yes:
|
||||||
|
cmd.extend(['--yes'])
|
||||||
|
cmd.extend(['--stack', stack.stack_name,
|
||||||
|
'--networks_file', tmp_net_data_path,
|
||||||
|
tmp_heat_nic_conf_path])
|
||||||
|
retcode = oooutils.run_command_and_log(self.log, cmd)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.remove(tmp_net_data_path)
|
||||||
|
except (IsADirectoryError, FileNotFoundError, PermissionError):
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.remove(tmp_heat_nic_conf_path)
|
||||||
|
except (IsADirectoryError, FileNotFoundError, PermissionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return j2_nic_conf_path if retcode == 0 else None
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
self.log.debug("take_action(%s)" % parsed_args)
|
self.log.debug("take_action(%s)" % parsed_args)
|
||||||
|
|
||||||
|
if not parsed_args.working_dir:
|
||||||
|
self.working_dir = oooutils.get_default_working_dir(
|
||||||
|
parsed_args.stack)
|
||||||
|
else:
|
||||||
|
self.working_dir = parsed_args.working_dir
|
||||||
|
oooutils.makedirs(self.working_dir)
|
||||||
|
|
||||||
self._setup_clients()
|
self._setup_clients()
|
||||||
stack = oooutils.get_stack(self.orchestration_client,
|
stack = oooutils.get_stack(self.orchestration_client,
|
||||||
parsed_args.stack)
|
parsed_args.stack)
|
||||||
|
@ -567,6 +629,19 @@ class ExtractProvisionedNode(command.Command):
|
||||||
"stack by setting the --roles-data argument.".format(
|
"stack by setting the --roles-data argument.".format(
|
||||||
parsed_args.stack))
|
parsed_args.stack))
|
||||||
|
|
||||||
|
if parsed_args.networks_file:
|
||||||
|
networks_file = os.path.abspath(parsed_args.networks_file)
|
||||||
|
with open(networks_file, 'r') as fd:
|
||||||
|
network_data = yaml.safe_load(fd.read())
|
||||||
|
else:
|
||||||
|
network_data = tht_j2_sources.get('networks_data')
|
||||||
|
if network_data is None:
|
||||||
|
raise oscexc.CommandError(
|
||||||
|
"Unable to extract. Network data not available in {} "
|
||||||
|
"stack output. Please provide the networks data for the "
|
||||||
|
"deployed stack by setting the --networks-data argument."
|
||||||
|
.format(parsed_args.stack))
|
||||||
|
|
||||||
# Convert role_data to a dict
|
# Convert role_data to a dict
|
||||||
role_data = {x['name']: x for x in role_data}
|
role_data = {x['name']: x for x in role_data}
|
||||||
|
|
||||||
|
@ -576,6 +651,7 @@ class ExtractProvisionedNode(command.Command):
|
||||||
stack, 'RoleNetIpMap') or {}
|
stack, 'RoleNetIpMap') or {}
|
||||||
parameters = stack.to_dict().get('parameters', {})
|
parameters = stack.to_dict().get('parameters', {})
|
||||||
parameter_defaults = stack.environment().get('parameter_defaults', {})
|
parameter_defaults = stack.environment().get('parameter_defaults', {})
|
||||||
|
resource_registry = stack.environment().get('resource_registry', {})
|
||||||
|
|
||||||
# list all baremetal nodes and map hostname to node name
|
# list all baremetal nodes and map hostname to node name
|
||||||
node_details = self.baremetal_client.node.list(detail=True)
|
node_details = self.baremetal_client.node.list(detail=True)
|
||||||
|
@ -629,10 +705,21 @@ class ExtractProvisionedNode(command.Command):
|
||||||
net_conf['template'] = parameters.get(
|
net_conf['template'] = parameters.get(
|
||||||
role_name + 'NetworkConfigTemplate')
|
role_name + 'NetworkConfigTemplate')
|
||||||
if net_conf['template'] is None:
|
if net_conf['template'] is None:
|
||||||
warnings.append(
|
net_conf['template'] = self._convert_heat_nic_conf_to_j2(
|
||||||
'WARNING: No network config found for role {}. Please '
|
stack, role_name, network_data, resource_registry,
|
||||||
'edit the file and set the path to the correct network '
|
parsed_args)
|
||||||
'config template.'.format(role_name))
|
|
||||||
|
if net_conf['template'] is None:
|
||||||
|
warnings.append(
|
||||||
|
'WARNING: No network config found for role {}. '
|
||||||
|
'Please edit the file and set the path to the correct '
|
||||||
|
'network config template.'.format(role_name))
|
||||||
|
else:
|
||||||
|
warnings.append(
|
||||||
|
'WARNING: Network config for role {} was '
|
||||||
|
'automatically converted from Heat template to '
|
||||||
|
'Ansible Jinja2 template. Please review the file: {}'
|
||||||
|
.format(role_name, net_conf['template']))
|
||||||
|
|
||||||
if parameters.get(role_name + 'NetworkDeploymentActions'):
|
if parameters.get(role_name + 'NetworkDeploymentActions'):
|
||||||
network_deployment_actions = parameters.get(
|
network_deployment_actions = parameters.get(
|
||||||
|
|
Loading…
Reference in New Issue