From 2ad0d0521d6c628266d80ec41f001ef1eb9bf973 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Mon, 7 May 2018 11:46:24 +0100 Subject: [PATCH] ansible: reload /etc/resolv.conf for each task. The OpenShift installer modifies /etc/resolv.conf then tests the new resolver configuration, however, there was no mechanism to reload resolv.conf in our reuseable interpreter. https://github.com/openshift/openshift-ansible/blob/release-3.9/roles/openshift_web_console/tasks/install.yml#L137 This inserts an explicit call to res_init() for every new style invocation, with an approximate cost of ~1usec on Linux since glibc verifies resolv.conf has changed before reloading it. There is little to be done for users of the thread-safe resolver APIs, their state is hidden from us. If bugs like that manifest, whack-a-mole style 'del sys.modules[thatmod]' patches may suffice. --- ansible_mitogen/runner.py | 14 +++++++ tests/ansible/integration/all.yml | 1 + .../ansible/integration/glibc_caches/all.yml | 2 + .../integration/glibc_caches/resolv_conf.yml | 38 +++++++++++++++++++ .../lib/modules/mitogen_test_gethostbyname.py | 16 ++++++++ 5 files changed, 71 insertions(+) create mode 100644 tests/ansible/integration/glibc_caches/all.yml create mode 100644 tests/ansible/integration/glibc_caches/resolv_conf.yml create mode 100644 tests/ansible/lib/modules/mitogen_test_gethostbyname.py diff --git a/ansible_mitogen/runner.py b/ansible_mitogen/runner.py index 30c0ca7b..bd2b0cc7 100644 --- a/ansible_mitogen/runner.py +++ b/ansible_mitogen/runner.py @@ -37,6 +37,7 @@ how to build arguments for it, preseed related data, etc. from __future__ import absolute_import import cStringIO +import ctypes import json import logging import os @@ -57,6 +58,17 @@ except ImportError: import ansible.module_utils.basic ansible.module_utils.basic._ANSIBLE_ARGS = '{}' +# For tasks that modify /etc/resolv.conf, non-Debian derivative glibcs cache +# resolv.conf at startup and never implicitly reload it. Cope with that via an +# explicit call to res_init() on each task invocation. BSD-alikes export it +# directly, Linux #defines it as "__res_init". +libc = ctypes.CDLL(None) +libc__res_init = None +for symbol in 'res_init', '__res_init': + try: + libc__res_init = getattr(libc, symbol) + except AttributeError: + pass LOG = logging.getLogger(__name__) @@ -397,6 +409,8 @@ class NewStyleRunner(ScriptRunner): # module, but this has never been a bug report. Instead act like an # interpreter that had its script piped on stdin. self._argv = TemporaryArgv(['']) + if libc__res_init: + libc__res_init() def revert(self): self._argv.revert() diff --git a/tests/ansible/integration/all.yml b/tests/ansible/integration/all.yml index 7e1d8689..3451f464 100644 --- a/tests/ansible/integration/all.yml +++ b/tests/ansible/integration/all.yml @@ -13,3 +13,4 @@ - import_playbook: remote_tmp/all.yml - import_playbook: runner/all.yml - import_playbook: ssh/all.yml +- import_playbook: glibc_caches/all.yml diff --git a/tests/ansible/integration/glibc_caches/all.yml b/tests/ansible/integration/glibc_caches/all.yml new file mode 100644 index 00000000..7d524540 --- /dev/null +++ b/tests/ansible/integration/glibc_caches/all.yml @@ -0,0 +1,2 @@ + +- import_playbook: resolv_conf.yml diff --git a/tests/ansible/integration/glibc_caches/resolv_conf.yml b/tests/ansible/integration/glibc_caches/resolv_conf.yml new file mode 100644 index 00000000..d1a466e9 --- /dev/null +++ b/tests/ansible/integration/glibc_caches/resolv_conf.yml @@ -0,0 +1,38 @@ + +# This cannot run against localhost, it damages /etc + +- name: integration/glibc_caches/resolv_conf.yml + gather_facts: true + become: true + hosts: test-targets + vars: + ansible_become_pass: has_sudo_pubkey_password + tasks: + + - debug: msg={{hostvars}} + - mitogen_test_gethostbyname: + name: www.google.com + register: out + when: ansible_virtualization_type == "docker" + + - shell: cp /etc/resolv.conf /tmp/resolv.conf + when: ansible_virtualization_type == "docker" + + - shell: echo > /etc/resolv.conf + when: ansible_virtualization_type == "docker" + + - mitogen_test_gethostbyname: + name: www.google.com + register: out + ignore_errors: true + when: ansible_virtualization_type == "docker" + + - shell: cat /tmp/resolv.conf > /etc/resolv.conf + when: ansible_virtualization_type == "docker" + + - assert: + that: + - out.failed + - '"Name or service not known" in out.msg or + "Temporary failure in name resolution" in out.msg' + when: ansible_virtualization_type == "docker" diff --git a/tests/ansible/lib/modules/mitogen_test_gethostbyname.py b/tests/ansible/lib/modules/mitogen_test_gethostbyname.py new file mode 100644 index 00000000..23dff9bd --- /dev/null +++ b/tests/ansible/lib/modules/mitogen_test_gethostbyname.py @@ -0,0 +1,16 @@ +#!/usr/bin/python + +# I am a module that indirectly depends on glibc cached /etc/resolv.conf state. + +import socket +from ansible.module_utils.basic import AnsibleModule + +def main(): + module = AnsibleModule(argument_spec={'name': {'type': 'str'}}) + try: + module.exit_json(addr=socket.gethostbyname(module.params['name'])) + except socket.error, e: + module.fail_json(msg=str(e)) + +if __name__ == '__main__': + main()