237 lines
7.1 KiB
Python
237 lines
7.1 KiB
Python
# 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()
|