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()