Store network_config opts to for inventory generation

Some node network_config options defined in the baremetal
deployment yaml definition can't be stored on neutron
resources as tags, because tag strings has a limited max
lenght.

With this change, these options are store in an ansible
inventory like structure in /var/lib/tripleo-network-config.
The data file is re-written on when nodes are provisioned.

tripleo-ansible-inventory will be extended to look for the
file and include the data in the generated inventory.

Partial-Implements: blueprint network-data-v2-ports
Change-Id: Iadf32e4426a763b0a39581eacc7af7d10d65d8fe
This commit is contained in:
Harald Jensås 2021-01-19 02:49:20 +01:00
parent e5c8e884f8
commit 26fe14a8d5
4 changed files with 392 additions and 0 deletions

View File

@ -62,3 +62,4 @@ mock_modules:
- tripleo_swift_tempurl
- tripleo_templates_upload
- tripleo_unmanaged_populate_environment
- tripleo_generate_inventory_network_config

View File

@ -0,0 +1,282 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021 OpenStack Foundation
# All Rights Reserved.
#
# 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 copy
import traceback
import yaml
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_generate_inventory_network_config
short_description: Generate network config for ansible inventory
version_added: "2.8"
description:
- Generates network config that cannot be stored on neutron port resources
for the ansible inventory.
options:
instances:
description:
- Data describing instances, node instances including networks and
network_config
type: list
elements: dict
suboptions:
hostname:
description:
- Node hostname
type: str
network_config:
description:
- Network configuration object
type: dict
suboptions:
default_route_network:
description:
- The network to use for the default route
type: list
default:
- ctlplane
template:
description:
- The nic config template
type: string
default: templates/net_config_bridge.j2
dns_search_domains:
description:
- A list of DNS search domains to be added (in order) to
resolv.conf.
type: list
default: []
physical_bridge_name:
description:
- An OVS bridge to create for accessing external networks.
type: string
default: br-ex
public_interface_name:
description:
- Which interface to add to the public bridge
type: string
default: nic1
network_deployment_actions:
description:
- When to apply network configuration changes, allowed values
are CREATE and UPDATE.
type: list
default: ['CREATE']
networks_skip_config:
description:
- List of networks that should be skipped when configuring node
networking
type: list
default: []
net_config_data_lookup:
description:
- Per node and/or per node group os-net-config nic mapping config
type: dict
bond_interface_ovs_options:
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
hostname_role_map:
description:
- Mapping of instance hostnames to role name
type: dict
author:
- Harald Jensås <hjensas@redhat.com>
'''
RETURN = '''
Controller:
hosts:
overcloud-controller-0:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
vars:
tripleo_network_config_with_ansible: true
Compute:
hosts:
overcloud-compute-0:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
overcloud-compute-1:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
vars:
tripleo_network_config_with_ansible: true
'''
EXAMPLES = '''
- name: Generate network config for ansible inventory
tripleo_generate_inventory_network_config:
instances:
- hostname: overcloud-controller-0
network_config:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
- hostname: overcloud-novacompute-0
network_config:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
- hostname: overcloud-novacompute-1
network_config:
template: templates/multiple_nics/multiple_nics.j2
physical_bridge_name: br-ex
public_interface_name: nic1
network_deployment_actions: ['CREATE']
net_config_data_lookup: {}
bond_interface_ovs_options: bond_mode=balance-slb
hostname_role_map:
overcloud-controller-0: Controller
overcloud-novacompute-0: Compute
overcloud-novacompute-1: Compute
'''
def set_network_config_defaults(module_opts, network_config):
net_config_opts = module_opts['instances']['suboptions']['network_config']
for k, v in net_config_opts['suboptions'].items():
default = v.get('default')
if default is not None:
network_config.setdefault(k, default)
def translate_opts_for_tripleo_network_config_role(network_config):
translation_map = dict(
template='tripleo_network_config_template',
physical_bridge_name='neutron_physical_bridge_name',
public_interface_name='neutron_public_interface_name',
network_deployment_actions=('tripleo_network_config_'
'network_deployment_actions'),
net_config_data_lookup='tripleo_network_config_os_net_config_mappings',
)
for key, value in copy.deepcopy(network_config).items():
if key not in translation_map:
continue
new_key = translation_map[key]
network_config.setdefault(new_key, value)
network_config.pop(key)
def generate_ansible_inventory_network_config(result, module_opts, instances,
hostname_role_map):
inventory = result['config']
roles = set(hostname_role_map.values())
for role in roles:
inventory.setdefault(role, dict())
inventory[role].setdefault('hosts', dict())
role_vars = inventory[role].setdefault('vars', dict())
role_vars.setdefault('tripleo_network_config_with_ansible', True)
for instance in instances:
if not instance.get('provisioned', True):
continue
hostname = instance['hostname']
role = hostname_role_map[hostname]
host = inventory[role]['hosts'].setdefault(hostname, dict())
network_config = instance.get('network_config', dict())
set_network_config_defaults(module_opts, network_config)
translate_opts_for_tripleo_network_config_role(network_config)
host.update(network_config)
# Delete empty roles, i.e no provisioned hosts.
for role in roles:
if not inventory[role]['hosts']:
del inventory[role]
result['changed'] = True
def run_module():
result = dict(
success=False,
changed=False,
error="",
config=dict(),
)
module_opts = yaml.safe_load(DOCUMENTATION)['options']
argument_spec = openstack_full_argument_spec(**module_opts)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False,
)
instances = module.params['instances']
hostname_role_map = module.params['hostname_role_map']
try:
generate_ansible_inventory_network_config(result, module_opts,
instances, hostname_role_map)
result['success'] = True
module.exit_json(**result)
except Exception:
result['error'] = traceback.format_exc()
result['msg'] = ("Error generating ansible inventory network config: "
"{}".format(traceback.format_exc().split('\n')[-2]))
module.fail_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View File

@ -176,6 +176,21 @@
content: "{{ network_ports_environment.environment | default({}) | to_nice_yaml(indent=2) }}"
when: manage_network_ports|default(false)
- name: Generate network config for ansible inventory
tripleo_generate_inventory_network_config:
instances: "{{ baremetal_instances.instances }}"
hostname_role_map: "{{ baremetal_instances.hostname_role_map }}"
register: inventory_network_config
when: manage_network_ports|default(false)
- name: Store inventory network config
copy:
dest: "{{ working_dir }}/inventory-network-config.yaml"
content: "{{ inventory_network_config.config | default({}) | to_nice_yaml(indent=2) }}"
force: true
mode: '0664'
when: manage_network_ports|default(false)
- name: Generate ansible inventory
tripleo_generate_ansible_inventory:
plan: "{{ stack_name }}"

View File

@ -0,0 +1,94 @@
# Copyright (c) 2021 OpenStack Foundation
# All Rights Reserved.
#
# 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 yaml
from tripleo_ansible.ansible_plugins.modules import (
tripleo_generate_inventory_network_config as plugin)
from tripleo_ansible.tests import base as tests_base
NETWORK_CONFIG = {
'template': '/foo/template.j2',
'net_config_data_lookup': {},
}
INSTANCE_WITH_NETWORK_CONFIG = {
'hostname': 'instance01',
'network_config': NETWORK_CONFIG,
}
INSTANCE_WITHOUT_NETWORK_CONFIG = {
'hostname': 'instance02',
}
UNPROVISIONED_INSTANCE = {
'hostname': 'instance03',
'provisioned': False,
'network_config': NETWORK_CONFIG,
}
FAKE_INSTANCES = [INSTANCE_WITH_NETWORK_CONFIG,
INSTANCE_WITHOUT_NETWORK_CONFIG,
UNPROVISIONED_INSTANCE]
FAKE_HOSTNAME_ROLE_MAP = {
'instance01': 'RoleA',
'instance02': 'RoleB',
'instance03': 'RoleC',
}
class TestTripleoGenerateInventoryNetworkConfig(tests_base.TestCase):
def test_generate_ansible_inventory_network_config(self):
result = {'changed': False, 'config': {}}
module_opts = yaml.safe_load(plugin.DOCUMENTATION)['options']
expected_inventory_network_config = {
'RoleA': {
'hosts': {
'instance01': {
'default_route_network': ['ctlplane'],
'dns_search_domains': [],
'networks_skip_config': [],
'neutron_physical_bridge_name': 'br-ex',
'neutron_public_interface_name': 'nic1',
'tripleo_network_config_network_deployment_actions':
['CREATE'],
'tripleo_network_config_os_net_config_mappings': {},
'tripleo_network_config_template': '/foo/template.j2'}
},
'vars': {
'tripleo_network_config_with_ansible': True}
},
'RoleB': {
'hosts': {
'instance02': {
'default_route_network': ['ctlplane'],
'dns_search_domains': [],
'networks_skip_config': [],
'neutron_physical_bridge_name': 'br-ex',
'neutron_public_interface_name': 'nic1',
'tripleo_network_config_network_deployment_actions':
['CREATE'],
'tripleo_network_config_template':
'templates/net_config_bridge.j2'}},
'vars': {
'tripleo_network_config_with_ansible': True}
}
}
plugin.generate_ansible_inventory_network_config(
result, module_opts, FAKE_INSTANCES, FAKE_HOSTNAME_ROLE_MAP)
self.assertEqual(expected_inventory_network_config, result['config'])