---
- name: Check sysctl settings for boolean values
  fail:
    msg: Boolean values are not allowed for sysctl settings
  when:
    - kernel_settings_sysctl != __kernel_settings_state_empty
    - (kernel_settings_sysctl | selectattr("value", "defined") |
      selectattr("value", "sameas", true) | list | length > 0) or
      (kernel_settings_sysctl | selectattr("value", "defined") |
      selectattr("value", "sameas", false) | list | length > 0)

- name: Set version specific variables
  include_tasks: set_vars.yml

- name: Ensure required packages are installed
  package:
    name: "{{ __kernel_settings_packages }}"
    state: present
    use: "{{ (__kernel_settings_is_ostree | d(false)) |
             ternary('ansible.posix.rhel_rpm_ostree', omit) }}"
  register: kernel_settings_package_result

- name: Handle reboot for transactional update systems
  when:
    - __kernel_settings_is_transactional | d(false)
    - kernel_settings_package_result is changed
  block:
    - name: Notify user that reboot is needed to apply changes
      debug:
        msg: >
          Reboot required to apply changes due to transactional updates.

    - name: Reboot transactional update systems
      reboot:
        msg: Rebooting the system to apply transactional update changes.
      when: kernel_settings_transactional_update_reboot_ok | bool

    - name: Fail if reboot is needed and not set
      fail:
        msg: >
          Reboot is required but not allowed. Please set
          'kernel_settings_transactional_update_reboot_ok' to proceed.
      when:
        - kernel_settings_transactional_update_reboot_ok is none

- name: Read tuned main config
  kernel_settings_get_config:
    path: "{{ __kernel_settings_tuned_main_conf_file }}"
  register: __kernel_settings_register_tuned_main

# this is the parent directory for the profile sub-directories
# if the dir is set in the config and that directory exists, use
# it - otherwise, use /etc/tuned/profiles, otherwise, use /etc/tuned
- name: Find tuned profile parent directory
  stat:
    path: "{{ item }}"
  when: item | length > 0
  loop:
    - "{{ __prof_from_conf }}"
    - "{{ __kernel_settings_tuned_dir ~ '/profiles' }}"
    - "{{ __kernel_settings_tuned_dir }}"
  vars:
    __data: "{{ __kernel_settings_register_tuned_main.data }}"
    __prof_from_conf: "{{ __data.get('profile_dirs', '').split(',')[-1] }}"
  register: __kernel_settings_find_profile_dirs

- name: Set tuned profile parent dir
  set_fact:
    __kernel_settings_profile_parent: "{{
      (__kernel_settings_find_profile_dirs.results |
      selectattr('stat', 'defined') | selectattr('stat.exists', 'defined') |
      selectattr('stat.exists') | selectattr('stat.path', 'defined') |
      map(attribute='stat.path') | list)[0] }}"

- name: Ensure required services are enabled and started
  service:
    name: "{{ item }}"
    state: started
    enabled: true
  loop: "{{ __kernel_settings_services }}"

- name: Ensure kernel settings profile directory exists
  file:
    path: "{{ __kernel_settings_profile_dir }}"
    state: directory
    mode: "0755"

- name: Get active_profile
  slurp:
    path: "{{ __kernel_settings_tuned_active_profile }}"
  register: __kernel_settings_tuned_current_profile

- name: Set active_profile
  set_fact:
    # not really invalid - see https://github.com/ansible/ansible-lint/issues/4702
    # noqa jinja[invalid]
    __kernel_settings_active_profile: "{{ __cur_profile
      if __kernel_settings_tuned_profile in __cur_profile
      else __cur_profile ~ ' ' ~ __kernel_settings_tuned_profile }}"
  vars:
    __cur_profile: "{{ __kernel_settings_tuned_current_profile.content |
      b64decode | trim }}"

- name: Ensure kernel_settings is in active_profile
  copy:
    content: >
      {{ __kernel_settings_active_profile }}
    dest: "{{ __kernel_settings_tuned_active_profile }}"
    mode: preserve
  register: __kernel_settings_register_profile

- name: Set profile_mode to manual
  copy:
    content: >
      manual
    dest: "{{ __kernel_settings_tuned_profile_mode }}"
    mode: preserve
  register: __kernel_settings_register_mode

- name: Get current config
  kernel_settings_get_config:
    path: "{{ __kernel_settings_profile_filename }}"
  register: __kernel_settings_profile_contents

- name: Initialize new sysctl
  set_fact:
    __kernel_settings_new_sysctl: "{{
      __kernel_settings_profile_contents.data.get('sysctl', {})
      if not kernel_settings_purge
      and kernel_settings_sysctl != __kernel_settings_state_empty
      and not __kernel_settings_previous_replaced in kernel_settings_sysctl
      else {} }}"

- name: Set new sysctl
  set_fact:
    __kernel_settings_new_sysctl: "{{ __kernel_settings_new_sysctl | combine(__new_item) }}"
  loop: "{{ [] if kernel_settings_sysctl == __kernel_settings_state_empty
    else kernel_settings_sysctl | rejectattr('previous', 'defined') | list }}"
  vars:
    __new_item: "{{ {item.name: __new_value} }}"
    __new_value: "{{ __kernel_settings_state_absent
      if item.state | d('present') == 'absent'
      else item.value | d(__kernel_settings_state_absent) }}"

- name: Initialize new sysfs
  set_fact:
    __kernel_settings_new_sysfs: "{{
      __kernel_settings_profile_contents.data.get('sysfs', {})
      if not kernel_settings_purge
      and kernel_settings_sysfs != __kernel_settings_state_empty
      and not __kernel_settings_previous_replaced in kernel_settings_sysfs
      else {} }}"

- name: Set new sysfs
  set_fact:
    __kernel_settings_new_sysfs: "{{ __kernel_settings_new_sysfs | combine(__new_item) }}"
  loop: "{{ [] if kernel_settings_sysfs == __kernel_settings_state_empty
    else kernel_settings_sysfs | rejectattr('previous', 'defined') | list }}"
  vars:
    __new_item: "{{ {item.name: __new_value} }}"
    __new_value: "{{ __kernel_settings_state_absent
      if item.state | d('present') == 'absent'
      else item.value | d(__kernel_settings_state_absent) }}"

- name: Apply kernel settings
  template:
    src: "{{ __kernel_settings_profile_src }}.j2"
    dest: "{{ __kernel_settings_profile_filename }}"
    mode: "0644"
  vars:
    # we don't have a way to compare an item to a dict - eq not available in el7
    # so assume if the value is a dict, it is the {"state": "absent"} dict
    # because "real" values should be scalars like strings, int, bool
    __sysctl_has_values: "{{ __kernel_settings_new_sysctl | dict2items | rejectattr('value', 'mapping') | list | length > 0 }}"
    __sysfs_has_values: "{{ __kernel_settings_new_sysfs | dict2items | rejectattr('value', 'mapping') | list | length > 0 }}"
    __systemd_old: "{{
      __kernel_settings_profile_contents.data.get('systemd', {}).get('cpu_affinity', '')
      if not kernel_settings_purge
      and kernel_settings_systemd_cpu_affinity != __kernel_settings_state_absent
      else '' }}"
    __systemd_new: "{{ kernel_settings_systemd_cpu_affinity
      if kernel_settings_systemd_cpu_affinity is not none and
      kernel_settings_systemd_cpu_affinity != __kernel_settings_state_absent and
      kernel_settings_systemd_cpu_affinity | length > 0
      else __systemd_old }}"
    __trans_huge_old: "{{
      __kernel_settings_profile_contents.data.get('vm', {}).get('transparent_hugepages', '')
      if not kernel_settings_purge
      and kernel_settings_transparent_hugepages != __kernel_settings_state_absent
      else '' }}"
    __trans_huge_new: "{{ kernel_settings_transparent_hugepages
      if kernel_settings_transparent_hugepages is not none and
      kernel_settings_transparent_hugepages != __kernel_settings_state_absent and
      kernel_settings_transparent_hugepages | length > 0
      else __trans_huge_old }}"
    __trans_defrag_old: "{{
      __kernel_settings_profile_contents.data.get('vm', {}).get('transparent_hugepage.defrag', '')
      if not kernel_settings_purge
      and kernel_settings_transparent_hugepages_defrag != __kernel_settings_state_absent
      else '' }}"
    __trans_defrag_new: "{{ kernel_settings_transparent_hugepages_defrag
      if kernel_settings_transparent_hugepages_defrag is not none and
      kernel_settings_transparent_hugepages_defrag != __kernel_settings_state_absent and
      kernel_settings_transparent_hugepages_defrag | length > 0
      else __trans_defrag_old }}"
  register: __kernel_settings_register_apply

# this will also apply the kernel_settings profile, so we
# can skip the apply profile step in this case
- name: Restart tuned to apply active profile, mode changes
  service:
    name: "{{ item }}"
    state: restarted
    enabled: true
  loop: "{{ __kernel_settings_services }}"
  when: __kernel_settings_register_profile is changed or
    __kernel_settings_register_mode is changed

- name: Tuned apply settings
  command: >-
    tuned-adm profile {{ __kernel_settings_active_profile | quote }}
  when:
    - not __kernel_settings_register_profile is changed
    - not __kernel_settings_register_mode is changed
    - __kernel_settings_register_apply is changed  # noqa no-handler
  changed_when: true

- name: Verify settings
  include_tasks: verify_settings.yml
  when: __kernel_settings_register_apply is changed  # noqa no-handler

# reboot not currently used - was used when the role could set
# some bootloader settings, but that was never supported, and
# we now have a dedicated bootloader role which is much better.
# The sysctl, sysfs, etc. settings are applied immediately,
# there is no need to reboot to apply those changes.
# so, keep this here since it is part of the public API, and
# in case we need it in the future
- name: Set the flag that reboot is needed to apply changes
  set_fact:
    kernel_settings_reboot_required: false

- name: Set flag to indicate changed for testing
  set_fact:
    __kernel_settings_changed: "{{
      __kernel_settings_register_profile is changed
      or __kernel_settings_register_mode is changed
      or __kernel_settings_register_apply is changed }}"
