Ansible: Find (first) free IP-address and set it - ansible

I have a couple of dozen Fedora-clients with DHCP-IP-addresses and I need to set them to static IPs. Usually there is (in this case) only one client per VLAN, but there can be more. The idea is to check if a IP-address (in the same range) is pingable and if not I set the clients IP to this address. This would be sufficient for my case but I'm happy to hear better suggestions.
Setting a given IP-address works fine, but how can I find the first "free" address and use it later in the playbook? So i guess: How can I register the first "failed" IP-address?
My ping-test-command
- name: "Test if the ip address is already online."
command: "ping -q -c 1 -W 1 192.168.178.{{ item }}"
register: res
loop: "{{ range(1,5) | list }}"
#failed_when: res.rc == 0
Output:
[user#host ansible]$ ansible-playbook change_network.yml
PLAY [all] **************************************************************************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************************************************************
ok: [192.168.178.40]
TASK [change_network : Test if the ip address is already online.] *******************************************************************************************************************************************
changed: [192.168.178.40] => (item=1)
changed: [192.168.178.40] => (item=2)
failed: [192.168.178.40] (item=3) => {"ansible_loop_var": "item", "changed": true, "cmd": ["ping", "-q", "-c", "1", "-W", "1", "192.168.178.3"], "delta": "0:00:01.003836", "end": "2020-12-30 12:04:18.700349", "item": 3, "msg": "non-zero return code", "rc": 1, "start": "2020-12-30 12:04:17.696513", "stderr": "", "stderr_lines": [], "stdout": "PING 192.168.178.3 (192.168.178.3) 56(84) bytes of data.\n\n--- 192.168.178.3 ping statistics ---\n1 packets transmitted, 0 received, 100% packet loss, time 0ms", "stdout_lines": ["PING 192.168.178.3 (192.168.178.3) 56(84) bytes of data.", "", "--- 192.168.178.3 ping statistics ---", "1 packets transmitted, 0 received, 100% packet loss, time 0ms"]}
failed: [192.168.178.40] (item=4) => {"ansible_loop_var": "item", "changed": true, "cmd": ["ping", "-q", "-c", "1", "-W", "1", "192.168.178.4"], "delta": "0:00:01.004021", "end": "2020-12-30 12:04:20.741041", "item": 4, "msg": "non-zero return code", "rc": 1, "start": "2020-12-30 12:04:19.737020", "stderr": "", "stderr_lines": [], "stdout": "PING 192.168.178.4 (192.168.178.4) 56(84) bytes of data.\n\n--- 192.168.178.4 ping statistics ---\n1 packets transmitted, 0 received, 100% packet loss, time 0ms", "stdout_lines": ["PING 192.168.178.4 (192.168.178.4) 56(84) bytes of data.", "", "--- 192.168.178.4 ping statistics ---", "1 packets transmitted, 0 received, 100% packet loss, time 0ms"]}
So 192.168.178.3 is the first free address and could be used in this example.
The second problem is, that I need to search in the correct range, i.e. replace the hardcoded "192.168.178" with an ansible variable (the first three octets of the DHCP-address stay the same). I tried something like from this question: Ansible increment IP address
- name: Append the last octet to the first 3 octets
set_fact: new_ip_address="{{ ansible_default_ipv4.address | regex_replace('(^.*\.).*$', '\\1') }}{{ item }}"
loop: "{{ range(1,5) | list }}"
- debug: var=new_ip_address
It works in this form, but I couldn't get this to work in my ping-command. Also maybe there is a prettier solution with a jinja-filter instead of the regex?

as serverfault question you can use nmap to issue a subnet scan.
Pay attentions: unless you know the networking infrastructure, there is still the possibility that picked IP address is used, firewall can block scanning.
To increase the probability that the found IP is really free, you can also use a DNS reverse query, to check if that IP has a PTR record:
- name: Scanning for free ip address
command:>
nmap -v -sn -n "{{ subnet }}" -oG - \
| awk '/Status: Down/{print $2}' \
| awk 'NR>2 {print last} {last=$0}'
register: ipscan
- name: Get fist free ip address
set_fact:
ipv4_address: "{{ ipscan.stdout_lines | first }}"
We need the second awk to remove the network address and broadcast address (first and last) from the output.
Checking also for reverse DNS on picked ip address:
- name: Retrieving host DNS Data.
set_fact:
vm_ipv4_dns_names: "{{ lookup('dig', ipv4_address ~ '/PTR').split(',') | map('regex_replace', '\\.?$', '' ) }}"
- name: Check reverse DNS returns data
fail:
msg: Picked ip address is on DNS
when: ('NXDOMAIN' not in vm_ipv4_dns_names)
I hope you find this useful.

Related

How to repeat the same command in creating the openstack backup?

I took volumes 'in-use' of OpenStack instance and filtered those volume ids into a file from which it has to make a backup
shell: openstack volume list | grep 'in-use' | awk '{print $2}' > /home/volumeid
shell: openstack volume backup create {{ item }}
with_items:
- /home/volumeid
error shows like
**failed: [controller2] (item=volumeid) => {"ansible_loop_var": "item", "changed": true, "cmd": "openstack volume backup create volumeid", "delta": "0:00:03.682611", "end": "2022-09-26 12:01:59.961613", "item": "volumeid", "msg": "non-zero return code", "rc": 1, "start": "2022-09-26 12:01:56.279002", "stderr": "No volume with a name or ID of 'volumeid' exists.", "stderr_lines": ["No volume with a name or ID of 'volumeid' exists."], "stdout": "", "stdout_lines": []}
failed: [controller1] (item=volumeid) => {"ansible_loop_var": "item", "changed": true, "cmd": "openstack volume backup create volumeid", "delta": "0:00:04.020051", "end": "2022-09-26 12:02:00.280130", "item": "volumeid", "msg": "non-zero return code", "rc": 1, "start": "2022-09-26 12:01:56.260079", "stderr": "No volume with a name or ID of 'volumeid' exists.", "stderr_lines": ["No volume with a name or ID of 'volumeid' exists."], "stdout": "", "stdout_lines": []}**
Can someone say how to create the volume backup from that file (which has volume ids) in the ansible playbook?
Currently, you are supplying only one element to the with_items, that is, /home/volumeid, meaning your loop will iterate only once for the file name and not its contents.
You need to use the file lookup if you are on localhost or the slurp module on the remote host. Example:
For the localhost:
- name: Show the volume id from the file
debug:
msg: "{{ item }}"
loop: "{{ lookup('file', '/home/volumeid').splitlines() }}"
For the remote host:
- name: Alternate if the file is on remote host
ansible.builtin.slurp:
src: /home/volumeid
register: vol_data
- name: Show the volume id from the file
debug:
msg: "{{ item }}"
loop: "{{ (vol_data['content'] | b64decode).splitlines() }}"
Just one line shell command:
openstack volume list --status in-use -c ID -f value | xargs -n1 openstack volume backup create
One advice, don't use the hardcode command like this grep 'in-use' or awk '{print $2}', openstack has it's optional arguments and output formatters, check it by openstack command [sub command] -h.

Ansible: pvchange with shell does not get executed

I'm not able to execute the shell command pvchange -u /dev/sde with ansible.
Can you pls help me to figure out what's wrong?
vars:
tgt_string_id: TGTDBID
action_vg: "clone_{{ tgt_string_id }}vg"
host_vars_dir: /home/abhn02a/ansible/host_vars
action_host: "{{ tgt_vm | default('localhost') }}"
tgt_disks: "/dev/sde /dev/sdf"
Tasks
- name: Display target disks
debug:
msg: "{{ item }}"
loop: "{{ tgt_disks.split(' ') }}"
- name: Change pv uuid
become: yes
shell: |
pvchange -u "{{ item }}"
loop: "{{ tgt_disks.split(' ') }}"
#changed_when: false
delegate_to: "{{ action_host }}"
Output:
......
.......
TASK [Display target disks]
*********************************************************************************************
ok: [localhost] => (item=/dev/sde) => {
    "msg": "/dev/sde"
}
ok: [localhost] => (item=/dev/sdf) => {
    "msg": "/dev/sdf"
}
!! This is not true, uuid stays same !!!
TASK [Change pv uuid]
***************************************************************************************************
changed: [localhost] => (item=/dev/sde)
changed: [localhost] => (item=/dev/sdf)
.......
.....
When I check the uuid of the disk, It has not been changed. Then, I type "pvchange -u /dev/sde", It changes the uuid of the disk with no problem!
Have ansible 2.9 with python2.7.5
Can you help, please?
(Oh, this is my 1st question on SO, sorry for the previous formatting)
Edit
So I have almost reproduced the error at home.
This happens when I try to do a pvchange on a cloned disk (But again, only with Ansible!!)
I have cloned 2 disks (/dev/xvdc and /dev/xvde) to (/dev/xvdf and /dev/xvdg), and re-run the task, and I have following output:
TASK [Change pv uuid]
*************************************************************************
failed: [localhost] (item=/dev/xvdf) => {"ansible_loop_var": "item", "changed": true, "cmd": "pvchange -u \"/dev/xvdf\"\n", "delta": "0:00:00.038053", "end": "2021-03-06 03:46:07.526239", "item": "/dev/xvdf", "msg": "non-zero return code", "rc": 5, "start": "2021-03-06 03:46:07.488186", "stderr": " Failed to find physical volume \"/dev/xvdf\".", "stderr_lines": [" Failed to find physical volume \"/dev/xvdf\"."], "stdout": " 0 physical volumes changed / 0 physical volumes not changed", "stdout_lines": [" 0 physical volumes changed / 0 physical volumes not changed"]}
This seems to be, because the cloned disks are still not "PVs" in LVM; I do not see /dev/xvdf and /dev/xvdg when I do pvs.
But at work, this was different, I could see the cloned disks in "pvs output"
Will check again next week.
Thank you
From the dub of the execution, you can see that the command executed is pvchange -u "/dev/xvdf", which raise an error message 'Failed to find physical volume "/dev/xvdf".', so looks like it doesn't like the quotes.
You can change your task to that, it should works better:
- name: Change pv uuid
become: yes
shell: |
pvchange -u {{ item }}
loop: "{{ tgt_disks.split(' ') }}"
pvscan --cache `, before doing` pvchange -u <PV>

Ansible get specific value from output

I have been pulling my hair out over something I thought would have been easy to do.
I have playbook:
- hosts: all
tasks:
- name: Get F5 connections
bigip_command:
commands: show ltm node /mypartition/myserver.domain.com
provider:
server: "F5server.domain.com"
user: ""
password: ""
validate_certs: "no"
server_port: 443
register: connections
delegate_to: localhost
- name: output
debug:
msg: "{{ connections }}"
When I run that playbook, it outputs this:
ok: [myserver.domain.com] => {
"msg": {
"changed": false,
"executed_commands": [
"tmsh -c \\\"show ltm node /mypartition/myserver.domain.com\\\""
],
"failed": false,
"stdout": [
"-------------------------------------------------------------\nLtm::Node: /mypartition/myserver.domain.com (123.45.67.89)\n-------------------------------------------------------------\nStatus \n Availability : available\n State : enabled\n Reason : Node address is available\n Monitor : /Common/gateway_icmp (default node monitor)\n Monitor Status : up\n Session Status : enabled\n \nTraffic ServerSide General\n Bits In 635.9M -\n Bits Out 2.8G -\n Packets In 102.8K -\n Packets Out 266.6K -\n Current Connections 7 -\n Maximum Connections 11 -\n Total Connections 6.5K -\n Total Requests - 13.0K\n Current Sessions - 0"
],
"stdout_lines": [
[
"-------------------------------------------------------------",
"Ltm::Node: /mypartition/myserver.domain.com (123.45.67.89)",
"-------------------------------------------------------------",
"Status ",
" Availability : available",
" State : enabled",
" Reason : Node address is available",
" Monitor : /Common/gateway_icmp (default node monitor)",
" Monitor Status : up",
" Session Status : enabled",
" ",
"Traffic ServerSide General",
" Bits In 635.9M -",
" Bits Out 2.8G -",
" Packets In 102.8K -",
" Packets Out 266.6K -",
" Current Connections 7 -",
" Maximum Connections 11 -",
" Total Connections 6.5K -",
" Total Requests - 13.0K",
" Current Sessions - 0"
]
]
}
}
My question is, how do I simply get the "Current Connections" value i.e. in this example it is 7 and store it in a variable.
I have tried various different solutions but nothing seems to work.
My Ansible version is 2.9
Can someone please help?
The task
- debug:
msg: "Current Connections: {{ item.split().2 }}"
loop: "{{ connections.stdout_lines.0 }}"
when: item is match('^ Current Connections(.*)$')
gives
"msg": "Current Connections: 7"
Use a regular expression to extract the value and set the variable "current_connections"
- name: Current Connections
vars:
pattern: '(?<=Current\sConnections)\s*\d+'
set_fact:
current_connections: "{{ connections.stdout | regex_search(pattern) }}"
- debug:
var: hostvars[inventory_hostname]['current_connections']

how to extract string from ansible regsiter variable in ansible

I have written the following ansible playbook to find the disk failure on the raid
- name: checking raid status
shell: "cat /proc/mdstat | grep nvme"
register: "array_check"
- debug:
msg: "{{ array_check.stdout_lines }}"
Following is the output I got
"msg": [
"md0 : active raid1 nvme0n1p1[0] nvme1n1p1[1]",
"md2 : active raid1 nvme1n1p3[1](F) nvme0n1p3[0]",
"md1 : active raid1 nvme1n1p2[1] nvme0n1p2[0]"
]
I want to extract the disk name which is failed from the register variable array_check.
How do I do this in the ansible? Can I use set_fact module in ansible? Can I use grep, awk, sed command on the register variable array_check
This is the playbook I am using to check the health status of a drive using smartctl
- name: checking the smartctl logs
shell: "smartctl -H /dev/{{ item }}"
with_items:
- nvme0
- nvme1
And I am facing the following error
(item=nvme0) => {"changed": true, "cmd": "smartctl -H /dev/nvme0", "delta": "0:00:00.090760", "end": "2019-09-05 11:21:17.035173", "failed": true, "item": "nvme0", "rc": 127, "start": "2019-09-05 11:21:16.944413", "stderr": "/bin/sh: 1: smartctl: not found", "stdout": "", "stdout_lines": [], "warnings": []}
(item=nvme1) => {"changed": true, "cmd": "smartctl -H /dev/nvme1", "delta": "0:00:00.086596", "end": "2019-09-05 11:21:17.654036", "failed": true, "item": "nvme1", "rc": 127, "start": "2019-09-05 11:21:17.567440", "stderr": "/bin/sh: 1: smartctl: not found", "stdout": "", "stdout_lines": [], "warnings": []}
The desired output should be something like this,
=== START OF SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED
Below is the complete playbook including the logic to execute multiple commands in a single task using with_items,
---
- hosts: raid_host
remote_user: ansible
become: yes
become_method: sudo
tasks:
- name: checking raid status
shell: "cat /proc/mdstat | grep 'F' | cut -d' ' -f6 | cut -d'[' -f1"
register: "array_check"
- debug:
msg: "{{ array_check.stdout_lines }}"
- name: checking the samrtctl logs for the drive
shell: "/usr/sbin/smartctl -H /dev/{{ item }} | tail -2|awk -F' ' '{print $6}'"
with_items:
- "nvme0"
- "nvme1"
register: "smartctl_status"

Difference filter not working in Ansible

I am trying to find which ports are available for my use. The logic goes like this, I am firs finding the used ports and providing a list of ports that I can use, the difference filter should filter out the ones that are available, but somehow it is not working.
Here is the code block:
- name: Gather occupied tcp v4 ports
shell: netstat -nlt| awk '{print $4}'|awk -F':' '{print $2}'
register: used_ports
- debug:
var: used_ports
- name: Difference
vars:
allowed_ports:
- 107
- 823
- 4750
set_fact:
bind_port: "{{ allowed_ports | difference(used_ports) | first }}"
- name: Show bind port
debug:
var: bind_port
Output:
ok: [] => {
"used_ports": {
"changed": true,
"cmd": "netstat -nlt| awk '{print $4}'|awk -F':' '{print $2}'",
"delta": "0:00:00.077467",
"end": "2018-08-12 15:25:04.477710",
"failed": false,
"rc": 0,
"start": "2018-08-12 15:25:04.400243",
"stderr": "",
"stderr_lines": [],
"stdout": ",
"stdout_lines": [
"",
"",
"107",
"202",
"106"
]
} }
TASK [serverbuild : Difference]
********************************************************************* ok: []
TASK [serverbuild : Show bind port]
***************************************************************** ok: [] => {
"bind_port": "107" }
Ideally it should not show 107 as it is already used. What am I doing wrong here?
There are two problems:
You should use used_ports.stdout_lines as an argument to the difference filter,
You should either define allowed_ports to contain strings, or map used_ports.stdout_lines to integers.
So:
- name: Difference
vars:
allowed_ports:
- "107"
- "823"
- "4750"
set_fact:
bind_port: "{{ allowed_ports | difference(used_ports.stdout_lines) | first }}"
or:
- name: Difference
vars:
allowed_ports:
- 107
- 823
- 4750
set_fact:
bind_port: "{{ allowed_ports | difference(used_ports.stdout_lines|map('int')) | first }}"

Resources