Simple ask: I want to delete some files if partition utilization goes over a certain percentage.
I have access to "size_total" and "size_available" via "ansible_mounts". i.e.:
ansible myhost -m setup -a 'filter=ansible_mounts'
myhost | success >> {
"ansible_facts": {
"ansible_mounts": [
{
"device": "/dev/mapper/RootVolGroup00-lv_root",
"fstype": "ext4",
"mount": "/",
"options": "rw",
"size_available": 5033046016,
"size_total": 8455118848
},
How do I access those values, and how would I perform actions conditionally based on them using Ansible?
Slava's answer definitely was on the right track, here is what I used:
- name: test for available disk space
assert:
that:
- not {{ item.mount == '/' and ( item.size_available < item.size_total - ( item.size_total|float * 0.8 ) ) }}
- not {{ item.mount == '/var' and ( item.size_available < item.size_total - ( item.size_total|float * 0.8 ) ) }}
with_items: ansible_mounts
ignore_errors: yes
register: disk_free
- name: free disk space
command: "/some/command/that/fixes/it"
when: disk_free|failed
The assert task simply tests for a condition, by setting ignore_errors, and registering the result of the test to a new variable we can perform a conditional task later in the play instead of just failing when the result of the assert fails.
The tests themselves could probably be written more efficiently, but at the cost of readability. So I didn't use a multiple-list loop in the example. In this case the task loops over each item in the list of mounted filesystems (an ansible-created fact, called ansible_mounts.)
By negating the test we avoid failing on file system mounts not in our list, then simple math handles the rest. The part that tripped me up was that the size_available and size_total variables were strings, so a jinja filter converts them to a float before calculating the percentage.
In my case, all I care about is the root partition. But I found when using the example from frameloss above, that I needed a negated 'or' condition, because each mount point will get tested against the assertion. If more than one mount point existed, then that meant the assertion would always fail. In my example, I'm testing for if the size_available is less than 50% of size_total directly, rather than calculate it as frameloss did.
Secondly, at least in the version of ansible I used, it was necessary to include the {{ }} around the variable in with_items. A mistake that I made that wasn't in the example above was not aligning the 'when' clause at the same indentation as the 'fail' directive. ( If that mistake is made, then the solution does not work... )
# This works with ansible 2.2.1.0
- hosts: api-endpoints
become: True
tasks:
- name: Test disk space available
assert:
that:
- item.mount != '/' or {{ item.mount == '/' and item.size_available > (item.size_total|float * 0.4) }}
with_items: '{{ ansible_mounts }}'
ignore_errors: yes
register: disk_free
- name: Fail when disk space needs attention
fail:
msg: 'Disk space needs attention.'
when: disk_free|failed
I didn't test it but I suggest to try something like this:
file:
dest: /path/to/big/file
state: absent
when: "{% for point in ansible_mounts %}{% if point.mount == '/' and point.size_available > (point.size_total / 100 * 85) %}true{% endif %}{% endfor %}" == "true"
In this example, we iterate over mount points and find "/", after that we calculate is there utilization goes over 85 percentage and prints "true" if it's true. Next, we compare that string and decide should this file be deleted.
Inspired by examples from the following blog: https://blog.codecentric.de/en/2014/08/jinja2-better-ansible-playbooks-templates/
My solution
- name: cleanup logs, free disk space below 20%
sudo: yes
command: find /var -name "*.log" \( \( -size +50M -mtime +7 \) -o -mtime +30 \) -exec truncate {} --size 0 \;
when: "item.mount == '/var' and ( item.size_available < item.size_total * 0.2 )"
with_items: ansible_mounts
This will truncate any *.log
files on the volume /var
that are either older than 7 days and greater than 50M or older than 30 days if the free disk space falls below 20%.