Handlers
Using Handlers
- A task that is triggered and is executed by a successful task.
Working with Handlers
- Define a notify statement at the level where the task is defined.
- The notify statement should list the name of the handler that is to be executed
- Handlers are listed at the end of the play.
- Make sure the name of the handler matches the name of the item that is called in the notify statement, because that is what the handler is looking for.
- Handlers can be specified as a list, so one task can call multiple handlers.
Lab
- Define the file index.html on localhost. Use this file in the second play to set up the web server.
- The handler is triggered from the task where the copy module is used to copy the index.html file.
- If this task is successful, the notify statement calls the handler.
- A second task is defined, which is intended to fail.
---
- name: create file on localhost
hosts: localhost
tasks:
- name: create index.html on localhost
copy:
content: "welcome to the webserver"
dest: /tmp/index.html
- name: set up web server
hosts: all
tasks:
- name: install httpd
yum:
name: httpd
state: latest
- name: copy index.html
copy:
src: /tmp/index.html
dest: /var/www/html/index.html
notify:
- restart_web
- name: copy nothing - intended to fail
copy:
src: /tmp/nothing
dest: /var/www/html/nothing.html
handlers:
- name: restart_web
service:
name: httpd
state: restarted-
All tasks up to copy index.html run successfully. However, the task copy nothing fails, which is why the handler does not run. The solution seems easy: the handler doesn’t run because the task that copies the file /tmp/nothing fails as the source file doesn’t exist.
-
Create the source file using
touch /tmp/nothingon the control host and run the task again. -
After creating the source file and running the playbook again, the handler still doesn’t run.
-
Handlers run only if the task that triggers them gives a changed status.
Run an ad hoc command to remove the /var/www/html/index.html file on the managed hosts and run the playbook again:
ansible ansible2 -m file -a "name=/var/www/html/index.html state=absent"
Run the playbook again and you’ll see the handler runs.
Understanding Handler Execution and Exceptions
When a task fails, none of the following tasks run. How does that make handlers different? A handler runs only on the success of a task, but the next task in the list also runs only if the previous task was successful. What, then, is so special about handlers?
The difference is in the nature of the handler.
- Handlers are meant to perform an extra action when a task makes a change to a host.
- Handler should be considered an extension to the regular task.
- A conditional task that runs only upon the success of a previous task.
Two methods to get Handlers to run even if a subsequent task fails:
force_handlers: true (More specific and preferred)
- Used in the play header to ensure that the handler will run even if a task fails.
ignore_errors: true
- Used in the play header to accomplish the same thing.
• Handlers are specified in a handlers section at the end of the play. • Handlers run in the order they occur in the handlers section and not in the order as triggered. • Handlers run only if the task calling them generates a changed status. • Handlers by default will not run if any task in the same play fails, unless force_handlers or ignore_errors are used. • Handlers run only after all tasks in the play where the handler is activated have been processed. You might want to define multiple plays to avoid this behavior.
Lab: Working with Handlers
1. Open a playbook with the name exercise73.yaml.
2. Define the play header:
---
- name: update the kernel
hosts: all
force_handlers: true
tasks:3. Add a task that updates the current kernel:
---
- name: update the kernel
hosts: all
force_handlers: true
tasks:
- name: update kernel
yum:
name: kernel
state: latest
notify: reboot_server4. Add a handler that reboots the server in case the kernel was successfully updated:
---
- name: update the kernel
hosts: all
force_handlers: true
tasks:
- name: update kernel
yum:
name: kernel
state: latest
notify: reboot_server
handlers:
- name: reboot_server
command: reboot5. Run the playbook using ansible-playbook exercise73.yaml andobserve its result. Notice that the handler runs only if the kernel was updated. If the kernel already was at the latest version, nothing has changed and the handler does not run. Also notice that it wasn’t really necessary to use force_handlers in the play header, but by using it anyway, at least you now know where to use it.
Dealing with Failures
Understanding Task Execution
- Tasks in Ansible playbooks are executed in the order they are specified.
- If a task in the playbook fails to execute on a host, the task generates an error and the play does not further execute on that specific host.
- This also goes for handlers: if any task that follows the task that triggers a handler fails, the handlers do not run.
- In both of these cases, it is important to know that the tasks that have run successfully still generate their result. Because this can give an unexpected result, it is important to always restore the original situation if that happens.
any_errors_fatal
- Used in the play header or on a block.
- Stop executing on all hosts when a failing task is encountered
Managing Task Errors
Generically, tasks can generate three different types of results. ok
- The tasks has run successfully but no changes were applied changed
- The task has run successfully and changes have been applied failed
- While running the task, a failure condition was encountered
ignore_errors: yes
- Keep running the playbook even if a task fails
force_handlers. If
- can be used to ensure that handlers will be executed, even if a failing task was encountered.
Lab: ignore_errors
---
- name: restart sshd only if crond is running
hosts: all
tasks:
- name: get the crond server status
command: /usr/bin/systemctl is-active crond
ignore_errors: yes
register: result
- name: restart sshd based on crond status
service:
name: sshd
state: restarted
when: result.rc == 0Lab: Forcing Handlers to Run
---
- name: create file on localhost
hosts: localhost
tasks:
- name: create index.html on localhost
copy:
content: "welcome to the webserver"
dest: /tmp/index.html
- name: set up web server
hosts: all
force_handlers: yes
tasks:
- name: install httpd
yum:
name: httpd
state: latest
- name: copy index.html
copy:
src: /tmp/index.html
dest: /var/www/html/index.html
notify:
- restart_web
- name: copy nothing - intended to fail
copy:
src: /tmp/nothing
dest: /var/www/html/nothing.html
handlers:
- name: restart_web
service:
name: httpd
state: restartedSpecifying Task Failure Conditions
failed_when
- conditional used to evaluate some expression.
- Set a failure condition on a task
Lab: failed_when
---
- name: demonstrating failed_when
hosts: all
tasks:
- name: run a script
command: echo hello world
ignore_errors: yes
register: command_result
failed_when: "’world’ in command_result.stdout"
- name: see if we get here
debug:
msg: second task executedfail module
- specify when a task fails.
- Using this module makes sense only if when is used to define the exact condition when a failure should occur.
Lab: Using the fail Module
---
- name: demonstrating the fail module
hosts: all
ignore_errors: yes
tasks:
- name: run a script
command: echo hello world
register: command_result
- name: report a failure
fail:
msg: the command has failed
when: "’world’ in command_result.stdout"
- name: see if we get here
debug:
msg: second task executed- The ignore_errors statement has movedfrom the task definition to the play header.
- Without this move, the message “second task executed” would never be shown because the fail module always generates a failure message.
- The main advantage of using the fail module instead of using failed_when is that the fail module can easily be used to set a clear failure message, which is not possible when using failed_when.
Managing Changed Status
In Ansible, there are commands that change something and commands that don’t. Some commands, however, are not very obvious in reporting their status.
Lab: Change status
---
- name: demonstrate changed status
hosts: all
tasks:
- name: check local time
command: date
register: command_result
- name: print local time
debug:
var: command_result.stdout-
Reports a changed status, even if nothing really was changed!
-
Managing the changed status can be useful in avoiding unexpected results while running a playbook.
changed_when
- If you set changed_when to false, the playbook reports only an ok or failed status and never reports a changed status.
Lab: Using changed_when
---
- name: demonstrate changed status
hosts: all
tasks:
- name: check local time
command: date
register: command_result
changed_when: false
- name: print local time
debug:
var: command_result.stdoutUsing Blocks
- Useful when working with conditional statements.
- A group of tasks to which a when statement can be applied.
- As a result, if a single condition is true, multiple tasks can be executed.
- To do so, between the tasks: statement in the play header and the actual tasks that run the specific modules, you can insert a block: statement.
Lab: Using Blocks
---
- name: simple block example
hosts: all
tasks:
- name: setting up http
block:
- name: installing http
yum:
name: httpd
state: present
- name: restart httpod
service:
name: httpd
state: started
when: ansible_distribution == "CentOS"- The when statement is applied at the same level as the block definition.
- When you define it this way, the tasks in the block are executed only if the when statement is true.
Using Blocks with rescue and always Statements
- Blocks can be used for simple error handling as well, in such a way that if any task that is defined in the block statement fails, the tasks that are defined in the rescue section are executed.
- Besides that, an always section can be used to define tasks that should always run, regardless of the success or failure of the tasks in the block.
Lab: Using Blocks, rescue, and always
- name: using blocks
hosts: all
tasks:
- name: intended to be successful
block:
- name: remove a file
shell:
cmd: rm /var/www/html/index.html
- name: printing status
debug:
msg: block task was operated
rescue:
- name: create a file
shell:
cmd: touch /tmp/rescuefile
- name: printing rescue status
debug:
msg: rescue task was operated
always:
- name: always write a message to logs
shell:
cmd: logger hello
- name: always printing this message
debug:
msg: this message is always printed- Run this twice to see the rescue. (The file is already created so a task in the block fails)
command_warnings=False
-
Setting in ansible.cfg to avoid seeing command module warning message.
-
you cannot use a block on a loop.
-
If you need to iterate over a list of values, think of using a different solution.