2
votes

I am writing a playbook over a list of inventory hosts for:-

If the host is reachable write "connection=1" and to a file. If the host is not reachable, write to same file "connection=0"

As per my understanding, Ansible does not store information of unreachable hots(when ssh fails) in an accessible way.

Could you please help me with it? My playbook is pasted below. shell task is not executed at all since the host is unreachable

Below is my playbook

- hosts: '{{ host }}'
  gather_facts: False
  vars:
    dest: /tmp/trace
  tasks:
    - copy:
        content: ''
        dest: "{{ dest }}"
      run_once: yes
      delegate_to: 127.0.0.1
    - shell: ping {{ inventory_hostname }}  -c 1
      register: ping_status
      ignore_errors:  yes
    - setup:
       filter: ansible_*
    - lineinfile:
        dest: "{{ dest }}"
        line: 'Host:{{ inventory_hostname }},OS:{{ ansible_distribution }},Kernel:{{ansible_kernel}},OSVersion:{{ansible_distribution_version}},FreeMemory:{{ansible_memfree_mb}},connection:{{ping_status.rc}}'
      ignore_errors: true
      delegate_to: 127.0.0.1
2
there can be no. of reasons for host unreachable. firstly what is the use case for the above ?error404

2 Answers

5
votes

There are a few issues with your playbook. The first is that you're trying to execute both a shell and a setup task on the remote host, which of course isn't going to work if that host isn't available.

It doesn't even make sense to run ping task on the remote host: you want to run that on your local host, using delegation. We can do something like this to record the availability of each host as a host variable:

---
- hosts: all
  gather_facts: false
  tasks:
    - delegate_to: localhost
      command: ping -c1 "{{ hostvars[inventory_hostname].ansible_host|default(inventory_hostname) }}"
      register: ping
      ignore_errors: true

    - set_fact:
        available: "{{ ping.rc == 0 }}"

You're trying to run the setup module against your remote host, but that only makes sense if the remote host is available, so we need to make that conditional on the result of our ping task:

- setup:
    filter: "ansible_*"
  when: ping.rc == 0

With that in place, we can generate a file with information about the availability of each host. I'm using lineinfile here because that's what you used in your example, but if I were writing this myself I would probably use a template task:

- hosts: localhost
  gather_facts: false
  tasks:
    - lineinfile:
        dest: ./available.txt
        line: "Host: {{ item }}, connection={{ hostvars[item].available }}"
        regexp: "Host: {{ item }}"
        create: true
      loop: "{{ groups.all }}"

Of course, in your example you're attempting to include a variety of other facts about the host:

        line: 'Host:{{ inventory_hostname }},OS:{{ ansible_distribution }},Kernel:{{ansible_kernel}},OSVersion:{{ansible_distribution_version}},FreeMemory:{{ansible_memfree_mb}},connection:{{ping_status.rc}}'

Those facts won't be available if the target host wasn't available, so you need to make all of that conditional using a {% if <condition> %}...{% endif %} construct:

line: "Host:{{ item }},connection:{{ hostvars[item].available }}{% if hostvars[item].available %},OS:{{ hostvars[item].ansible_distribution }},Kernel:{{ hostvars[item].ansible_kernel }},OSVersion:{{ hostvars[item].ansible_distribution_version }},FreeMemory:{{ hostvars[item].ansible_memfree_mb }}{% endif %}"

This makes the final playbook look like this:

---
- hosts: all
  gather_facts: false
  tasks:
    - delegate_to: localhost
      command: ping -c1 "{{ hostvars[inventory_hostname].ansible_host|default(inventory_hostname) }}"
      register: ping
      ignore_errors: true

    - set_fact:
        available: "{{ ping.rc == 0 }}"

    - setup:
      when: ping.rc == 0

- hosts: localhost
  gather_facts: false
  tasks:
    - lineinfile:
        dest: ./available.txt
        line: "Host:{{ item }},connection:{{ hostvars[item].available }}{% if hostvars[item].available %},OS:{{ hostvars[item].ansible_distribution }},Kernel:{{ hostvars[item].ansible_kernel }},OSVersion:{{ hostvars[item].ansible_distribution_version }},FreeMemory:{{ hostvars[item].ansible_memfree_mb }}{% endif %}"
        regexp: "Host: {{ item }}"
        create: true
      loop: "{{ groups.all }}"
0
votes

Unreachable hosts are accessible. You can get them indirectly from magic variables.

Basically, if the setup module fails, it's most likely because the server was unreachable. In this case, not even the 'always' block that I used will capture this error, because Failed/Unreachable hosts are not considered ‘active’ (https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html).

However, the way I use to get the unreachable hosts is to loop the groups['all'] magic variable. This variable contains all the hosts of the play, whether they were reachable or not.

So, the main idea is to write your variables (distribution, kernel, etc) for all hosts, and if the variables are not defined, then it was because the setup task failed. You can take advantage of this and use the default() filter to default the values to whatever you wish (I am using 'NA' in this case, and 'unreachable' for the last variable).

Please let me know what you think of this.

- hosts: all
  gather_facts: False
  vars:
    dest: /tmp/trace
  tasks:
  - block:
    - copy:
        content: ''
        dest: "{{ dest }}"
      run_once: yes
      delegate_to: 127.0.0.1
    - setup:
       filter: ansible_*
    - set_fact: connection_status="reachable"
    always:
    - lineinfile:
        dest: "{{ dest }}"
        line: 'Host:{{ item }},OS:{{ hostvars[item][ansible_distribution] | default('NA') }},Kernel:{{ hostvars[item][ansible_kernel] | default('NA') }},OSVersion:{{ hostvars[item][ansible_distribution_version] | default('NA') }},FreeMemory:{{ hostvars[item][ansible_memfree_mb] | default('NA') }},connection:{{ hostvars[item][connection_status] | default('unreachable') }}'
      loop: "{{ groups['all'] }}"
      run_once: true
      delegate_to: 127.0.0.1