Browse Source

Add os-net-config mappings support

Adds an ansible plug-in that can process the input
in THT parameter 'NetConfigDataLookup' used for the
OsNetConfigMappings resource in tripleo-heat-templates.

Add tasks in the tripleo_network_config role to call the
plug-in and write the result into the default mapping
file for os-net-config.

Change-Id: I968771376cdb3cd2e8e576a4de226b1eccebb23a
Blueprint: bp/nova-less-deploy
changes/69/749669/6
Harald Jensås 2 years ago
parent
commit
6bd7eeb7cc
  1. 161
      tripleo_ansible/ansible_plugins/modules/tripleo_os_net_config_mappings.py
  2. 1
      tripleo_ansible/roles/tripleo_network_config/defaults/main.yml
  3. 2
      tripleo_ansible/roles/tripleo_network_config/molecule/default/prepare.yml
  4. 20
      tripleo_ansible/roles/tripleo_network_config/tasks/main.yml
  5. 83
      tripleo_ansible/tests/modules/test_tripleo_os_net_config_mappings.py

161
tripleo_ansible/ansible_plugins/modules/tripleo_os_net_config_mappings.py

@ -0,0 +1,161 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat, Inc.
# 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.
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
import copy
import os
import subprocess
import yaml
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_os_net_config_mappings
author:
- Harald Jensås (hjensas@redhat.com)
version_added: '2.8'
short_description: Configure os-net-config mappings for nodes or node groups
notes: []
description:
- This module creates os-net-config mapping for nodes or node groups based on
the input data provided. MAC addresses or DMI table strings can be used
to identify specific nodes or node groups. See manual page for DMIDECODE(8)
for a list of DMI table strings that can be used.
options:
net_config_data_lookup:
description:
- Per node and/or per node group configuration map
type: dict
'''
EXAMPLES = '''
- name: Map os-net-config nicX abstraction using MAC address
tripleo_os_net_config_mappings:
net_config_data_lookup:
overcloud-controller-0:
nic1: "00:c8:7c:e6:f0:2e"
overcloud-compute-13:
nic1: "00:18:7d:99:0c:b6"
- name: Interface name to os-net-config nicX abstraction using system-uuid
tripleo_os_net_config_mappings:
net_config_data_lookup:
overcloud-controller-0:
dmiString: 'system-uuid'
id: 'A8C85861-1B16-4803-8689-AFC62984F8F6'
nic1: em3
nic2: em1
nic3: em2
nic4: em4
- name: Interface name to os-net-config nicX abstraction for node groups using system-product-name
tripleo_os_net_config_mappings:
net_config_data_lookup:
nodegroup-dell-poweredge-r630:
dmiString: "system-product-name"
id: "PowerEdge R630"
nic1: em3
nic2: em1
nic3: em2
nodegroup-cisco-ucsb-b200-m4:
dmiString: "system-product-name"
id: "UCSB-B200-M4"
nic1: enp7s0
nic2: enp6s0
'''
RETURN = '''
mapping:
description:
- Dictionary with os-net-config mapping data that can be written to the
os-net-config mapping file.
returned: when mapping match present in net_config_data_lookup
type: dict
'''
def _get_interfaces():
eth_addr = [
# cast to lower case for MAC address match
open('/sys/class/net/{}/address'.format(x)).read().strip().lower()
for x in os.listdir('/sys/class/net/')]
eth_addr = list(filter(None, eth_addr))
return eth_addr
def _get_mappings(data):
eth_addr = _get_interfaces()
for node in data:
iface_mapping = copy.deepcopy(data[node])
if 'dmiString' in iface_mapping:
del iface_mapping['dmiString']
if 'id' in iface_mapping:
del iface_mapping['id']
# Match on mac addresses first - cast all to lower case
lc_iface_mapping = copy.deepcopy(iface_mapping)
for key, x in lc_iface_mapping.items():
lc_iface_mapping[key] = x.lower()
if any(x in eth_addr for x in
lc_iface_mapping.values()):
return {'interface_mapping': lc_iface_mapping}
# If data contain dmiString and id keys, try to match node(group)
if 'dmiString' in data[node] and 'id' in data[node]:
ps = subprocess.Popen(
['dmidecode', '--string', data[node]['dmiString']],
stdout=subprocess.PIPE, universal_newlines=True)
out, err = ps.communicate()
# See LP#1816652
if data[node].get('id').lower() == out.rstrip().lower():
return {'interface_mapping': lc_iface_mapping}
def run(module):
results = dict(
changed=False,
mapping=None,
)
data = module.params['net_config_data_lookup']
if isinstance(data, dict) and data:
results['mapping'] = _get_mappings(data)
results['changed'] = True if results['mapping'] else False
module.exit_json(**results)
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=False,
)
run(module)
if __name__ == '__main__':
main()

1
tripleo_ansible/roles/tripleo_network_config/defaults/main.yml

@ -27,3 +27,4 @@ tripleo_network_config_hide_sensitive_logs: true
tripleo_network_config_interface_name: nic1
tripleo_network_config_manage_service: true
tripleo_network_config_network_deployment_actions: []
tripleo_network_config_os_net_config_mappings: {}

2
tripleo_ansible/roles/tripleo_network_config/molecule/default/prepare.yml

@ -20,6 +20,8 @@
tasks:
- import_role:
name: test_deps
vars:
test_deps_setup_tripleo: true
- name: Ensure legacy scripts installed
package:
name: network-scripts

20
tripleo_ansible/roles/tripleo_network_config/tasks/main.yml

@ -53,6 +53,26 @@
mode: 0755
when: not ansible_check_mode|bool
- name: Create /etc/os-net-config directory
become: true
file:
path: /etc/os-net-config
state: directory
recurse: true
- name: Create os-net-config mappings from lookup data
tripleo_os_net_config_mappings:
net_config_data_lookup:
"{{ tripleo_network_config_os_net_config_mappings }}"
when: not ansible_check_mode|bool
register: os_net_config_mappings_result
- name: Write os-net-config mappings file /etc/os-net-config/mapping.yaml
copy:
content: "{{ os_net_config_mappings_result.mapping | to_nice_yaml }}"
dest: /etc/os-net-config/mapping.yaml
when: os_net_config_mappings_result.changed|bool
- name: Run NetworkConfig script
shell: /var/lib/tripleo-config/scripts/run_os_net_config.sh
async: "{{ tripleo_network_config_async_timeout }}"

83
tripleo_ansible/tests/modules/test_tripleo_os_net_config_mappings.py

@ -0,0 +1,83 @@
# Copyright 2019 Red Hat, Inc.
# 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.
from tripleo_ansible.ansible_plugins.modules import (
tripleo_os_net_config_mappings)
from tripleo_ansible.tests import base as tests_base
import mock
@mock.patch('tripleo_ansible.ansible_plugins.modules.'
'tripleo_os_net_config_mappings._get_interfaces', autospec=True)
@mock.patch('subprocess.Popen', autospec=True)
class TestTripleoOsNetConfigMappings(tests_base.TestCase):
def test_mac_mappings_match(self, mock_Popen, mock_get_ifaces):
module = mock.MagicMock()
module.params = {
'net_config_data_lookup': {
'node0': {'nic1': 'aa:bb:cc:dd:ee:ff',
'nic2': 'ff:ee:dd:cc:bb:aa'},
'node1': {'nic1': '0a:0b:0c:0d:0e:0f',
'nic2': 'f0:e0:d0:c0:b0:a0'}
}
}
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
mock_get_ifaces.side_effect = ['aa:bb:cc:dd:ee:ff', 'ff:ee:dd:cc:bb:aa']
expected = module.params['net_config_data_lookup']['node0']
tripleo_os_net_config_mappings.run(module)
mock_exit.assert_called_once_with(
changed=True, mapping={'interface_mapping': expected})
def test_mac_mappings_no_match(self, mock_Popen, mock_get_ifaces):
module = mock.MagicMock()
module.params = {
'net_config_data_lookup': {
'node0': {'nic1': 'aa:bb:cc:dd:ee:ff',
'nic2': 'ff:ee:dd:cc:bb:aa'},
'node1': {'nic1': '0a:0b:0c:0d:0e:0f',
'nic2': 'f0:e0:d0:c0:b0:a0'}
}
}
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
mock_get_ifaces.side_effect = ['01:02:03:04:05:06', '10:20:30:40:50:60']
tripleo_os_net_config_mappings.run(module)
mock_exit.assert_called_once_with(changed=False, mapping=None)
def test_dmi_type_string_match(self, mock_Popen, mock_get_ifaces):
module = mock.MagicMock()
module.params = {
'net_config_data_lookup': {
'node2': {'dmiString': 'foo-dmi-type',
'id': 'bar-dmi-id',
'nic1': 'em3',
'nic2': 'em4'},
'node3': {'nic1': '0a:0b:0c:0d:0e:0f',
'nic2': 'f0:e0:d0:c0:b0:a0'}
}
}
mock_exit = mock.MagicMock()
module.exit_json = mock_exit
expected = {'nic1': 'em3',
'nic2': 'em4'}
mock_return = mock.MagicMock()
mock_return.return_value.communicate.return_value = ('bar-dmi-id', '')
mock_Popen.side_effect = mock_return
tripleo_os_net_config_mappings.run(module)
mock_exit.assert_called_once_with(
changed=True, mapping={'interface_mapping': expected})
Loading…
Cancel
Save