Ansible interface for os-net-config

This patch implements an Ansible interface to os-net-config.

* os_net_config module; which is in charge of running os-net-config
  tool. It supports a few options now (from os-net-config CLI):

  - cleanup, to cleanup unconfigured interfaces
  - config_file, to specify the os-net-config configuration file
  - debug, to enable debug level logging
  - detailed_exit_codes, to exit os-net-config with code 2 if files were
    modified. Note: the module will still return rc 0 if no error
    otherwise Ansible pukes.
  - safe_defaults, which will try to apply safe defaults if the provided
    configuration failed to execute. It'll basically run dhcp on all
    discovered interfaces.

  The module returns "rc" as the return code.
  The module has molecule testing, and we re-use the same script to
  generate safe defaults for os-net-config.

* update tripleo_network_config role; in charge of TripleO Network
  configuration.

  - Adding tripleo_network_config_legacy_script parameter, true by
    default so the legacy shell script is run.
  - Adding tripleo_network_config_safe_defaults since some network
    configs don't need default safe defaults.
  - Updating main, to run the newly created os_net_config module if
    tripleo_network_config_legacy_script is False.
  - New playbook, os_net_config.yml, running os-net-config with the
    provided config.

Change-Id: I1d0f1ab91c9cf290f70a554bfca9b508afc2c34a
This commit is contained in:
Emilien Macchi 2020-08-28 17:22:11 -04:00 committed by Rabi Mishra
parent 325d080583
commit a4d205f378
10 changed files with 478 additions and 25 deletions

View File

@ -0,0 +1,14 @@
==============================
Module - tripleo_os_net_config
==============================
This module provides for the following ansible plugin:
* tripleo_os_net_config
.. ansibleautoplugin::
:module: tripleo_ansible/ansible_plugins/modules/tripleo_os_net_config.py
:documentation: true
:examples: true

View File

@ -25,3 +25,4 @@ jsonschema # MIT
# Upstream requirements from constraints.txt
tripleo-common
os-net-config # Apache-2.0

View File

@ -0,0 +1,236 @@
# 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.
import json
import os
import subprocess
import time
import yaml
from ansible.module_utils.basic import AnsibleModule
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: tripleo_os_net_config
author:
- OpenStack TripleO Contributors
version_added: '1.0'
short_description: Execute os-net-config tool.
notes: []
requirements:
- os-net-config
description:
- Configure host network interfaces using a JSON config file format.
options:
cleanup:
description:
- Cleanup unconfigured interfaces.
type: bool
default: false
config_file:
description:
- Path to the configuration file.
type: str
default: /etc/os-net-config/config.yaml
debug:
description:
- Print debug output.
type: bool
default: false
detailed_exit_codes:
description:
- If enabled an exit code of '2' means that files were modified.
type: bool
default: false
safe_defaults:
description:
- If enabled, safe defaults (DHCP for all interfaces) will be applied in
case of failuring while applying the provided net config.
type: bool
default: false
"""
EXAMPLES = """
- name: Create network configs with defaults
tripleo_os_net_config:
"""
RETURN = """
rc:
description:
- Integer for the return code
returned: always
type: int
stdout:
description:
- The command standard output
returned: always
type: str
stderr:
description:
- The command standard error
returned: always
type: str
"""
DEFAULT_CFG = '/etc/os-net-config/dhcp_all_interfaces.yaml'
def _run_os_net_config(config_file, cleanup=False, debug=False,
detailed_exit_codes=False, noop=False):
# Build os-net-config command
argv = ['os-net-config --config-file {}'.format(config_file)]
if cleanup:
argv.append('--cleanup')
if debug:
argv.append('--debug')
if detailed_exit_codes:
argv.append('--detailed-exit-codes')
if noop:
argv.append('--noop')
cmd = " ".join(argv)
# Apply the provided network configuration
run = subprocess.run(cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
return cmd, run
def _apply_safe_defaults(debug=False):
_generate_default_cfg()
cmd, run = _run_os_net_config(config_file=DEFAULT_CFG, cleanup=True,
debug=debug, detailed_exit_codes=True)
return cmd, run
def _generate_default_cfg():
with open(DEFAULT_CFG, "w") as config_file:
config_file.write('# autogenerated safe defaults file which'
'will run dhcp on discovered interfaces\n\n')
network_interfaces = []
for i in os.listdir('/sys/class/net/'):
excluded_ints = ['lo', 'vnet']
if i in excluded_ints:
continue
mac_addr_type = int(open('/sys/class/net/{}/'
'addr_assign_type'.format(i)).read().strip())
if mac_addr_type != 0:
print('Device {} has generated MAC, skipping.'.format(i))
continue
try:
open('/sys/class/net/{}/'
'device/physfn'.format(i))
print("Device ({}) is a SR-IOV VF, skipping.".format(i))
continue
except FileNotFoundError:
pass
retries = 10
has_link = _has_link(i)
while has_link and retries > 0:
cmd = 'ip link set dev {} up &>/dev/null'.format(i)
subprocess.run(cmd, shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
has_link = _has_link(i)
if has_link:
break
time.sleep(1)
retries -= 1
if has_link:
network_interface = {
'type': 'interface',
'name': i,
'use_dhcp': True
}
network_interfaces.append(network_interface)
network_config = {'network_config': network_interfaces}
with open(DEFAULT_CFG, "ab") as config_file:
config_file.write(json.dumps(network_config, indent=2).encode('utf-8'))
def _has_link(interface):
try:
has_link = int(open('/sys/class/net/{}/'
'carrier'.format(interface)).read().strip())
except FileNotFoundError:
has_link = 0
if has_link == 1:
return True
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=True,
)
results = dict(
changed=False
)
# parse args
args = module.params
# Set parameters
cleanup = args['cleanup']
config_file = args['config_file']
debug = args['debug']
detailed_exit_codes = args['detailed_exit_codes']
safe_defaults = args['safe_defaults']
return_codes = [0]
if detailed_exit_codes:
return_codes.append(2)
# Apply the provided network configuration
cmd, run = _run_os_net_config(config_file, cleanup, debug,
detailed_exit_codes,
module.check_mode)
results['stderr'] = run.stderr
results['stdout'] = run.stdout
if run.returncode not in return_codes and not module.check_mode:
results['failed'] = True
results['rc'] = run.returncode
results['msg'] = ("Running %s failed with return code %s." % (
cmd, run.returncode))
if safe_defaults:
module.warn("Error applying the provided network configuration, "
"safe defaults will be applied in best effort.")
# Best effort to restore safe networking defaults to allow
# an operator to ssh the node and debug if needed.
_apply_safe_defaults(debug)
else:
results['rc'] = 0
results['msg'] = ("Successfully run %s." % cmd)
if run.returncode == 2 and detailed_exit_codes:
# NOTE: dprince this udev rule can apparently leak DHCP processes?
# https://bugs.launchpad.net/tripleo/+bug/1538259
# until we discover the root cause we can simply disable the
# rule because networking has already been configured at this point
udev_file = '/etc/udev/rules.d/99-dhcp-all-interfaces.rules'
if os.path.isfile(udev_file):
os.remove(udev_file)
results['changed'] = True
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,68 @@
---
- name: Converge
hosts: all
become: true
tasks:
- name: create os-net-config config file
tempfile:
state: file
prefix: os-net-config
register: onc_cfg
# This is a simple test to reconfigure the loopback interface
# because it's not easy to know what nics are available on the host
# and not breaking the current network configs.
- name: write os-net-config config file
copy:
content:
network_config:
-
type: interface
name: lo
use_dhcp: false
use_dhcpv6: false
addresses:
-
ip_netmask: 127.0.0.1/8
dest: "{{ onc_cfg.path }}"
- name: Create safe os-net-config defaults for all interfaces
become: true
shell: |
set -eux
cat > {{ onc_cfg.path }} <<EOF_CAT
network_config:
EOF_CAT
for iface in $(ls /sys/class/net | grep -v -e ^lo$ -e ^vnet$); do
mac_addr_type="$(cat /sys/class/net/${iface}/addr_assign_type)"
vf_parent="/sys/class/net/${iface}/device/physfn"
if [ "$mac_addr_type" != "0" ]; then
echo "Device has generated MAC, skipping."
elif [[ -d $vf_parent ]]; then
echo "Device (${iface}) is a SR-IOV VF, skipping."
else
HAS_LINK="$(cat /sys/class/net/${iface}/carrier || echo 0)"
TRIES=10
while [ "$HAS_LINK" == "0" -a $TRIES -gt 0 ]; do
# Need to set the link up on each iteration
ip link set dev $iface up &>/dev/null
HAS_LINK="$(cat /sys/class/net/${iface}/carrier || echo 0)"
if [ "$HAS_LINK" == "1" ]; then
break
else
sleep 1
fi
TRIES=$(( TRIES - 1 ))
done
if [ "$HAS_LINK" == "1" ] ; then
cat >> {{ onc_cfg.path }} <<EOF_CAT
-
type: interface
name: $iface
use_dhcp: true
EOF_CAT
fi
fi
done
- name: apply network config
tripleo_os_net_config:
config_file: "{{ onc_cfg.path }}"
debug: true

View File

@ -0,0 +1,50 @@
---
driver:
name: delegated
options:
managed: false
login_cmd_template: >-
ssh
-o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no
-o Compression=no
-o TCPKeepAlive=yes
-o VerifyHostKeyDNS=no
-o ForwardX11=no
-o ForwardAgent=no
{instance}
ansible_connection_options:
ansible_connection: ssh
log: true
platforms:
- name: instance
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
fact_caching_connection: /tmp/molecule/facts
inventory:
hosts:
all:
hosts:
instance:
ansible_host: localhost
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
name: tripleo_os_net_config
test_sequence:
- prepare
- converge
verifier:
name: testinfra

View File

@ -0,0 +1,23 @@
---
# 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.
- name: Prepare
hosts: all
roles:
- role: test_deps
test_deps_extra_packages:
- os-net-config
test_deps_setup_tripleo: true

View File

@ -25,6 +25,8 @@ tripleo_network_config_bridge_name: br-ex
tripleo_network_config_debug: "{{ (ansible_verbosity | int) >= 2 | bool }}"
tripleo_network_config_hide_sensitive_logs: true
tripleo_network_config_interface_name: nic1
tripleo_network_config_legacy_script: true
tripleo_network_config_manage_service: true
tripleo_network_config_network_deployment_actions: []
tripleo_network_config_os_net_config_mappings: {}
tripleo_network_config_safe_defaults: true

View File

@ -38,21 +38,6 @@
- name: NetworkConfig
become: true
block:
- name: Create /var/lib/tripleo-config/scripts directory
file:
path: /var/lib/tripleo-config/scripts
state: directory
setype: container_file_t
selevel: s0
recurse: true
- name: Render NetworkConfig script
template:
dest: /var/lib/tripleo-config/scripts/run_os_net_config.sh
src: "{{ tripleo_network_config_script_path }}"
mode: 0755
when: not ansible_check_mode|bool
- name: Create /etc/os-net-config directory
become: true
file:
@ -73,17 +58,48 @@
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 }}"
poll: "{{ tripleo_network_config_async_poll }}"
environment:
bridge_name: "{{ tripleo_network_config_bridge_name }}"
interface_name: "{{ tripleo_network_config_interface_name }}"
register: NetworkConfig_result
- name: Manage NetworkConfig with legacy run_os_net_config.sh
block:
- name: Create /var/lib/tripleo-config/scripts directory
file:
path: /var/lib/tripleo-config/scripts
state: directory
setype: container_file_t
selevel: s0
recurse: true
- name: Render NetworkConfig script
template:
dest: /var/lib/tripleo-config/scripts/run_os_net_config.sh
src: "{{ tripleo_network_config_script_path }}"
mode: 0755
when: not ansible_check_mode|bool
- name: Run NetworkConfig script
shell: /var/lib/tripleo-config/scripts/run_os_net_config.sh
async: "{{ tripleo_network_config_async_timeout }}"
poll: "{{ tripleo_network_config_async_poll }}"
environment:
bridge_name: "{{ tripleo_network_config_bridge_name }}"
interface_name: "{{ tripleo_network_config_interface_name }}"
register: NetworkConfig_result
when:
- not ansible_check_mode|bool
failed_when: NetworkConfig_result.rc is not defined
when:
- not ansible_check_mode|bool
failed_when: NetworkConfig_result.rc is not defined
- tripleo_network_config_legacy_script|bool
- name: Manage NetworkConfig with tripleo_os_net_config module
block:
- name: Remove /var/lib/tripleo-config/scripts directory
file:
path: /var/lib/tripleo-config/scripts
state: absent
- name: Run NetworkConfig with tripleo_os_net_config
include_tasks: os_net_config.yml
when:
- not tripleo_network_config_legacy_script|bool
- name: Write rc of NetworkConfig script
copy:
@ -92,11 +108,15 @@
when:
- NetworkConfig_result.rc is defined
# This task can be removed once we stop supporting the legacy script.
# tripleo_os_net_config module already prints out stdout/stderr and fails
# when there is a failure, we don't need an extra task for it.
- name: NetworkConfig stdout
debug:
var: NetworkConfig_result.stderr_lines
failed_when: NetworkConfig_result.rc != 0
when:
- tripleo_network_config_legacy_script|bool
- NetworkConfig_result.rc is defined
# os-net-config currently relies on the legacy network

View File

@ -0,0 +1,38 @@
---
# 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.
#
# Apply network configuration with os-net-config.
#
- name: Apply os-net-config configuration
become: true
block:
- name: Render network_config
no_log: "{{ tripleo_network_config_hide_sensitive_logs | bool }}"
template:
src: "{{ tripleo_network_config_script_path }}"
dest: /etc/os-net-config/config.json
mode: 0600
backup: true
- name: Run tripleo_os_net_config_module with network_config
tripleo_os_net_config:
config_file: /etc/os-net-config/config.json
debug: "{{ tripleo_network_config_debug|bool }}"
detailed_exit_codes: true
safe_defaults: "{{ tripleo_network_config_safe_defaults | bool }}"
async: "{{ tripleo_network_config_async_timeout }}"
poll: "{{ tripleo_network_config_async_poll }}"
register: NetworkConfig_result

View File

@ -360,6 +360,7 @@
- job:
files:
- ^tripleo_ansible/roles/tripleo_network_config/.*
- ^tripleo_ansible/ansible_plugins/modules/tripleo_os_net_config.py
name: tripleo-ansible-centos-8-molecule-tripleo_network_config
parent: tripleo-ansible-centos-8-base
vars: