Skip to content

Commit 0069bc9

Browse files
committed
fix(ansible:opennebula_guest): make tuned-adm verify pass on virtualized AMD hosts
Stock TuneD `virtual-guest` inherits `[cpu] boost=1` from `throughput-performance`. Inside QEMU/KVM guests on AuthenticAMD hosts the kernel exposes neither /sys/devices/system/cpu/cpufreq/boost nor /sys/devices/system/cpu/intel_pstate/no_turbo, so the knob is not settable -- apply emits a warning and `tuned-adm verify` always fails with "device cpuN: 'boost' = 'None', expected '1'", which trips the strict assertion in this role. On Intel hosts the stock profile works because intel_pstate/no_turbo is present. Fix: probe both sysfs knobs; when neither is exposed, install a sibling TuneD profile `almalinux-virtual-guest` whose tuned.conf is just: [main] include=virtual-guest [cpu] replace=1 and activate it. Inheriting the stock profile means every [sysctl], [vm], [disk] (etc.) block flows through verbatim, so future upstream changes to virtual-guest.tuned.conf are picked up automatically. `replace=1` is TuneD's documented mechanism to discard the inherited [cpu] plugin config, so the plugin has nothing to apply or verify -- correct for a virtualized CPU where none of the cpufreq/intel_pstate knobs are exposed anyway. On Intel hosts (knob exposed) no sibling profile is installed and the active profile remains `virtual-guest`. The profile is installed under /etc/tuned/profiles/<name>/ on TuneD >= 2.24 (AL10 / Kitten 10) and under /etc/tuned/<name>/ on older TuneD (AL8 / AL9), detected via the presence of /usr/lib/tuned/profiles/virtual-guest/tuned.conf. Also harden the verify step: * 5x3s retry around `tuned-adm verify` with `failed_when: false`, absorbing the first-start race where verify can run before the daemon finishes replaying every knob. * On persistent failure, the final assert's fail_msg embeds the verify stdout/stderr and the last 16 KiB of /var/log/tuned/tuned.log (verify's own stdout is only a generic "Verification failed" banner; the specific plugin/knob that differs is logged to tuned.log), so any future drift surfaces the offending setting directly in the Packer build output.
1 parent 48a4b44 commit 0069bc9

1 file changed

Lines changed: 120 additions & 3 deletions

File tree

  • ansible/roles/opennebula_guest/tasks

ansible/roles/opennebula_guest/tasks/main.yml

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,97 @@
5555
enabled: true
5656
state: started
5757

58+
# Detect whether the kernel exposes the cpufreq 'boost' knob. The stock
59+
# virtual-guest profile inherits [cpu] boost=1 from throughput-performance.
60+
# Inside QEMU/KVM guests on AuthenticAMD hosts neither cpufreq/boost nor
61+
# intel_pstate/no_turbo is exposed, so TuneD cannot set the knob (harmless
62+
# apply-time warning) and `tuned-adm verify` always fails with
63+
# "device cpuN: 'boost' = 'None', expected '1'". If we detect this, we
64+
# install a sibling profile 'almalinux-virtual-guest' that includes stock
65+
# virtual-guest verbatim but replaces [cpu] with an empty section, and
66+
# switch the active profile to it.
67+
- name: Probe cpufreq boost sysfs knobs
68+
ansible.builtin.stat:
69+
path: "{{ item }}"
70+
loop:
71+
- /sys/devices/system/cpu/cpufreq/boost
72+
- /sys/devices/system/cpu/intel_pstate/no_turbo
73+
register: tuned_boost_knobs
74+
75+
- name: Decide whether TuneD 'boost' knob is settable on this host
76+
ansible.builtin.set_fact:
77+
tuned_boost_available: "{{ tuned_boost_knobs.results | selectattr('stat.exists') | list | length > 0 }}"
78+
79+
- name: Pick expected TuneD profile for this host
80+
ansible.builtin.set_fact:
81+
tuned_expected_profile: "{{ 'virtual-guest' if tuned_boost_available else 'almalinux-virtual-guest' }}"
82+
83+
# TuneD's profile layout changed in 2.24: stock profiles moved from
84+
# /usr/lib/tuned/<name>/ to /usr/lib/tuned/profiles/<name>/, and the user
85+
# override path moved from /etc/tuned/<name>/ to /etc/tuned/profiles/<name>/.
86+
# AL8/AL9 ship the old layout, AL10/Kitten 10 (tuned >= 2.24) ship the new.
87+
# Detect which layout is in use so our custom profile lands where TuneD
88+
# actually looks; using the wrong dir would be a silent no-op.
89+
- name: Probe TuneD profile layout (new /profiles/ subdir vs old flat)
90+
ansible.builtin.stat:
91+
path: /usr/lib/tuned/profiles/virtual-guest/tuned.conf
92+
register: tuned_new_layout_stat
93+
when: not tuned_boost_available
94+
95+
- name: Compute {{ tuned_expected_profile | default('almalinux-virtual-guest') }} profile directory
96+
ansible.builtin.set_fact:
97+
tuned_profile_dir: >-
98+
{{ ('/etc/tuned/profiles/' if tuned_new_layout_stat.stat.exists else '/etc/tuned/')
99+
~ tuned_expected_profile }}
100+
when: not tuned_boost_available
101+
102+
- name: Ensure {{ tuned_profile_dir | default('/etc/tuned/<profiles>/almalinux-virtual-guest') }} directory exists
103+
ansible.builtin.file:
104+
path: "{{ tuned_profile_dir }}"
105+
state: directory
106+
owner: root
107+
group: root
108+
mode: '0755'
109+
when: not tuned_boost_available
110+
111+
- name: Install {{ tuned_expected_profile | default('almalinux-virtual-guest') }} TuneD profile
112+
ansible.builtin.copy:
113+
dest: "{{ tuned_profile_dir }}/tuned.conf"
114+
owner: root
115+
group: root
116+
mode: '0644'
117+
content: |
118+
# Managed by the cloud-images opennebula_guest Ansible role.
119+
#
120+
# Sibling of stock 'virtual-guest' that disables the [cpu] plugin.
121+
# On QEMU/KVM guests without cpufreq/intel_pstate exposure (e.g.
122+
# AuthenticAMD hosts) stock virtual-guest fails `tuned-adm verify`
123+
# because its inherited [cpu] boost=1 expectation cannot be
124+
# satisfied on a missing sysfs knob. This profile inherits every
125+
# stock setting verbatim via `include=virtual-guest`, so future
126+
# upstream changes to virtual-guest.tuned.conf flow through
127+
# automatically -- only [cpu] replace=1 is added to drop the
128+
# unverifiable plugin.
129+
#
130+
# `tuned-adm active` will report 'almalinux-virtual-guest' rather
131+
# than 'virtual-guest' on these hosts.
132+
[main]
133+
summary=AlmaLinux virtual-guest for virtualized CPUs without cpufreq/boost exposure
134+
include=virtual-guest
135+
136+
[cpu]
137+
replace=1
138+
when: not tuned_boost_available
139+
140+
# Unconditional: on Intel hosts this is a force-reapply of virtual-guest
141+
# (helps with the first-start race where plugins load late); on AMD
142+
# virtualized hosts it switches the active profile to our sibling one
143+
# before we assert on it.
144+
- name: Activate {{ tuned_expected_profile }} TuneD profile
145+
ansible.builtin.command:
146+
cmd: "tuned-adm profile {{ tuned_expected_profile }}"
147+
changed_when: false
148+
58149
- name: Get current TuneD profile
59150
ansible.builtin.command:
60151
cmd: tuned-adm active
@@ -63,20 +154,46 @@
63154

64155
- name: Test if TuneD profile is active
65156
ansible.builtin.assert:
66-
that: "tuned_adm_active.stdout == 'Current active profile: virtual-guest'"
67-
fail_msg: Configured active profile is not same as current one
157+
that: "tuned_adm_active.stdout == ('Current active profile: ' ~ tuned_expected_profile)"
158+
fail_msg: "Configured active profile is not same as current one (expected: {{ tuned_expected_profile }})"
68159
success_msg: The configured TuneD profile is current
69160

161+
# Retry around the first-start race where `tuned-adm verify` can run before
162+
# the daemon finishes replaying every knob. `failed_when: false` keeps the
163+
# loop alive across the intermediate non-zero exits; the strict assert below
164+
# still fails the build if verification never succeeds.
70165
- name: Get status of TuneD settings
71166
ansible.builtin.command:
72167
cmd: tuned-adm verify
73168
register: tuned_adm_verify
74169
changed_when: false
170+
failed_when: false
171+
retries: 5
172+
delay: 3
173+
until: "'Verification succeeded' in tuned_adm_verify.stdout"
174+
175+
# `tuned-adm verify`'s stdout is just a generic "Verification failed ..."
176+
# banner; the specific plugin/knob that differs is only written to
177+
# /var/log/tuned/tuned.log. Capture the tail unconditionally so the strict
178+
# assert below can surface it in its fail_msg when verification fails.
179+
- name: Capture TuneD log tail for diagnostics
180+
ansible.builtin.command:
181+
cmd: tail -c 16384 /var/log/tuned/tuned.log
182+
register: tuned_log_tail
183+
changed_when: false
184+
failed_when: false
75185

76186
- name: Verify if settings on the TuneD profile is applied
77187
ansible.builtin.assert:
78188
that: "'Verification succeeded, current system settings match the preset profile.' in tuned_adm_verify.stdout"
79-
fail_msg: Current system settings does not match current active profile
189+
fail_msg: |-
190+
Current system settings does not match current active profile.
191+
--- tuned-adm verify stdout ---
192+
{{ tuned_adm_verify.stdout | default('<no stdout>') }}
193+
--- tuned-adm verify stderr ---
194+
{{ tuned_adm_verify.stderr | default('<no stderr>') }}
195+
--- /var/log/tuned/tuned.log (last 16 KiB) ---
196+
{{ tuned_log_tail.stdout | default('<log unavailable>') }}
80197
success_msg: Current system settings matches current active profile
81198

82199
- name: Regenerate all initramfs images

0 commit comments

Comments
 (0)