Series overview | Previous: Automate Cisco Switchports with Ansible and Jinja2 | Next: Test and Deploy Network Automation Safely
Part 4 of 5: In Part 4, we connect the event queue to Ansible. A Bash worker extracts the switch address, MAC address, and interface, selects the appropriate playbook, records the result, and runs continuously as a systemd service.
Safety note: Use an isolated lab, replace every example value, protect all credentials, maintain console access, and back up configurations before allowing automated changes.
Create the Log-Processing Service
The next component polls the MySQL queue every five seconds and processes messages sequentially. Despite the original section name, this implementation uses polling rather than inotify. Unknown messages are deleted by the sample script; change that behavior if your retention or audit requirements require them to be preserved.
sudo nano /usr/local/bin/monitor_logs.sh
Create the script below and replace the example database password. For production, move the credential to a root-readable MySQL option file or secrets manager instead of leaving it in the script.
#!/bin/bash
# MySQL connection details
MYSQL_USER="rsyslog_user"
MYSQL_PASS="REPLACE_WITH_A_LONG_RANDOM_PASSWORD"
MYSQL_DB="rsyslog"
MYSQL_HOST="127.0.0.1" # or 'localhost'
LOG_FILE="/var/log/inotify.log"
Device_Log_File_Path="/var/log/rsyslog/"
# MySQL query to get rows where status is blank (NULL or empty)
QUERY="SELECT CONCAT(id,',',FromHost,',',Message) AS formatted_row FROM SystemEvents WHERE status IS NULL OR status = '';"
# Loop to continuously check the database
while true; do
# Query MySQL for rows where status is blank
rows=$(mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -D "$MYSQL_DB" -N -e "$QUERY")
if [ ! -z "$rows" ]; then
echo "Found rows with blank status. Executing Ansible playbook for each row..." >>"$LOG_FILE"
# Mac Address Lists
macOuiList="/etc/ansible/OUIs.txt"
macDeviceList="/etc/ansible/DeviceMacs.txt"
macUnknownList="/var/log/rsyslog/unknowndevices.log"
wirelessList="/etc/ansible/Wireless.txt"
# Loop through each row and execute the playbook
while read -r row; do
echo "$row" >>"$LOG_FILE"
# Extract the row details (id and msg)
#id=$(echo "$row" | awk '{print $1}')
#msg=$(echo "$row" | awk '{print $2}')
IFS=',' read -r id ip_address msg <<<"$row"
# Print the current row being processed
echo "Processing row ID: $id with message: $msg from $ip_address" >>"$LOG_FILE"
# Extract IP address, MAC address, and interface using regex
macaddress=$(echo "$msg" | grep -oE '[[:xdigit:]]{4}\.[[:xdigit:]]{4}\.[[:xdigit:]]{4}' | head -n 1)
interface=$(echo "$msg" | grep -oE '(FiveGigabitEthernet|TenGigabitEthernet|GigabitEthernet|Gi|Fi|Te)[0-9]+/[0-9]+/[0-9]+' | head -n 1)
dt=$(date '+%m/%d/%Y %H:%M:%S')
echo "current data is: $ip_address,$macaddress,$interface" >>"$LOG_FILE"
# Verify device is known and port should be configured. If not remove sql entry.
if [[ "$msg" == *"psecure-violation"* ]] || [[ "$msg" == *"Security violation occurred"* ]]; then
echo "$dt: Port: $interface disabled on $ip_address running dot1xreset script" >>"$LOG_FILE"
# Trigger the Ansible playbook (modify as needed)
ansible-playbook /etc/ansible/playbooks/resetdot1x.yml -e "ip_address=$ip_address macaddress=$macaddress interface=$interface sql_id=$id" >>"$Device_Log_File_Path/portsecure.log" 2>&1
# After execution, update the row's status to 'processed'
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='started-reset-dot1x-playbook' WHERE id=$id;"
echo "Row ID: $id processed successfully!" >>"$LOG_FILE"
elif [[ "$msg" == *"BLOCK_BPDUGUARD"* ]]; then
echo "$dt: Port: $interface will be disabled" >>"$LOG_FILE"
#Trigger the Ansible playbook
ansible-playbook /etc/ansible/playbooks/bpduguardDisable.yml -e "ip_address=$ip_address interface=$interface sql_id=$id" >>"$Device_Log_File_Path/bpduportdisable.log" 2>&1
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='started-port-disable-script-bpdu' WHERE id=$id;"
echo "Row ID: $id processed successfully!" >>"$LOG_FILE"
elif grep -qi "${macaddress:0:7}" "$macOuiList" || grep -qi "$macaddress" "$macDeviceList"; then
# Execute the Ansible playbook for the current row (you can pass extra vars if needed)
if [[ "$msg" == *"DOT1X-5-FAIL"* ]]; then
echo "$dt: Port: $interface in guest vlan on $ip_address running port_config script" >>"$LOG_FILE"
# Trigger the Ansible playbook (modify as needed)
ansible-playbook /etc/ansible/playbooks/switch_port_config.yml -vvvv -e "ip_address=$ip_address macaddress=$macaddress interface=$interface sql_id=$id" >>"$Device_Log_File_Path/$macaddress.log" 2>&1
# After execution, update the row's status to 'processed'
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='started-switchport-config-playbook' WHERE id=$id;"
echo "Row ID: $id processed successfully!" >>"$LOG_FILE"
else
echo "dt: Port: $interface on $ip_address did not match a rule with message: $msg" >>"$LOG_FILE"
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='Did not meet any playbook requirements.' WHERE id=$id;"
fi
elif grep -qi "${macaddress:0:7}" "$wirelessList"; then
# Execute the Ansible playbook for the current row (you can pass extra vars if needed)
if [[ "$msg" == *"DOT1X-5-FAIL"* ]]; then
echo "$dt: Port: $interface in guest vlan on $ip_address running port_config script" >>"$LOG_FILE"
# Trigger the Ansible playbook (modify as needed)
ansible-playbook /etc/ansible/playbooks/switch_port_config.yml -vvvv -e "ip_address=$ip_address macaddress=$macaddress interface=$interface sql_id=$id" >>"$Device_Log_File_Path/$macaddress.log" 2>&1
# After execution, update the row's status to 'processed'
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='started-switchport-config-playbook' WHERE id=$id;"
echo "Row ID: $id processed successfully!" >>"$LOG_FILE"
else
echo "dt: Port: $interface on $ip_address did not match a rule with message: $msg" >>"$LOG_FILE"
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "UPDATE SystemEvents SET status='Did not meet any playbook requirements.' WHERE id=$id;"
fi
else
if grep -qi "$macaddress" "$macUnknownList"; then
echo "done"
else
echo "$macaddress does not exist in OUIs.txt or DeviceMacs.txt" >> "$macUnknownList"
fi
mysql -u "$MYSQL_USER" -p"$MYSQL_PASS" -h "$MYSQL_HOST" -D "$MYSQL_DB" -e "Delete FROM SystemEvents WHERE id=$id;"
fi
done <<<"$rows"
else
echo "No rows with blank status found. Waiting for updates..."
fi
# Wait 5 seconds before checking again (can be adjusted)
sleep 5
done
We need to update the file permissions to make it executable.
sudo chmod +x /usr/local/bin/monitor_logs.sh
After the script is finalized, we will transform it into a service that automatically restarts each time the server reboots.
sudo nano /etc/systemd/system/monitor_logs.service
[Unit]
Description=Monitor logs for Ansible trigger
After=network-online.target mysql.service rsyslog.service
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/monitor_logs.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Reload systemd, enable and start the service, and then check its status and recent logs.
sudo systemctl daemon-reload
sudo systemctl enable --now monitor_logs.service
sudo systemctl status monitor_logs.service
sudo journalctl -u monitor_logs.service -n 50 --no-pager
If you modify the unit later, reload systemd and restart the service:
sudo systemctl daemon-reload
sudo systemctl restart monitor_logs.service
Expected Result
At the end of this part, the worker starts automatically, processes queued lab events, and exposes useful status information through systemd and its log files.
Troubleshooting
- Run the script interactively before enabling the service so parsing and permissions are visible.
- Use
sudo journalctl -u monitor_logs.service -n 100 --no-pagerwhen the service restarts or fails. - If variables are empty, compare the regular expressions against an unmodified syslog message from the affected switch model.
Series overview | Previous: Automate Cisco Switchports with Ansible and Jinja2 | Next: Test and Deploy Network Automation Safely
Leave a Reply