1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2018, Christian Kotte <christian.kotte@gmx.de>
5#
6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
7
8from __future__ import absolute_import, division, print_function
9__metaclass__ = type
10
11
12ANSIBLE_METADATA = {
13    'metadata_version': '1.1',
14    'status': ['preview'],
15    'supported_by': 'community'
16}
17
18DOCUMENTATION = r'''
19---
20module: vmware_host_hyperthreading
21short_description: Enables/Disables Hyperthreading optimization for an ESXi host system
22description:
23- This module can be used to enable or disable Hyperthreading optimization for ESXi host systems in given vCenter infrastructure.
24- It also checks if Hyperthreading is activated/deactivated and if the host needs to be restarted.
25- The module informs the user if Hyperthreading is enabled but inactive because the processor is vulnerable to L1 Terminal Fault (L1TF).
26version_added: 2.8
27author:
28- Christian Kotte (@ckotte)
29notes:
30- Tested on vSphere 6.5
31requirements:
32- python >= 2.6
33- PyVmomi
34options:
35  state:
36     description:
37        - Enable or disable Hyperthreading.
38        - You need to reboot the ESXi host if you change the configuration.
39        - Make sure that Hyperthreading is enabled in the BIOS. Otherwise, it will be enabled, but never activated.
40     type: str
41     choices: [ enabled, disabled ]
42     default: 'enabled'
43  esxi_hostname:
44    description:
45    - Name of the host system to work with.
46    - This parameter is required if C(cluster_name) is not specified.
47    type: str
48  cluster_name:
49    description:
50    - Name of the cluster from which all host systems will be used.
51    - This parameter is required if C(esxi_hostname) is not specified.
52    type: str
53extends_documentation_fragment: vmware.documentation
54'''
55
56EXAMPLES = r'''
57- name: Enable Hyperthreading for an host system
58  vmware_host_hyperthreading:
59    hostname: '{{ vcenter_hostname }}'
60    username: '{{ vcenter_username }}'
61    password: '{{ vcenter_password }}'
62    esxi_hostname: '{{ esxi_hostname }}'
63    state: enabled
64    validate_certs: no
65  delegate_to: localhost
66
67- name: Disable Hyperthreading for an host system
68  vmware_host_hyperthreading:
69    hostname: '{{ vcenter_hostname }}'
70    username: '{{ vcenter_username }}'
71    password: '{{ vcenter_password }}'
72    esxi_hostname: '{{ esxi_hostname }}'
73    state: disabled
74    validate_certs: no
75  delegate_to: localhost
76
77- name: Disable Hyperthreading for all host systems from cluster
78  vmware_host_hyperthreading:
79    hostname: '{{ vcenter_hostname }}'
80    username: '{{ vcenter_username }}'
81    password: '{{ vcenter_password }}'
82    cluster_name: '{{ cluster_name }}'
83    state: disabled
84    validate_certs: no
85  delegate_to: localhost
86'''
87
88RETURN = r'''
89results:
90    description: metadata about host system's Hyperthreading configuration
91    returned: always
92    type: dict
93    sample: {
94        "esxi01": {
95            "msg": "Hyperthreading is already enabled and active for host 'esxi01'",
96            "state_current": "active",
97            "state": "enabled",
98        },
99    }
100'''
101
102try:
103    from pyVmomi import vim, vmodl
104except ImportError:
105    pass
106
107from ansible.module_utils.basic import AnsibleModule
108from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec
109from ansible.module_utils._text import to_native
110
111
112class VmwareHostHyperthreading(PyVmomi):
113    """Manage Hyperthreading for an ESXi host system"""
114    def __init__(self, module):
115        super(VmwareHostHyperthreading, self).__init__(module)
116        cluster_name = self.params.get('cluster_name')
117        esxi_host_name = self.params.get('esxi_hostname')
118        self.hosts = self.get_all_host_objs(cluster_name=cluster_name, esxi_host_name=esxi_host_name)
119        if not self.hosts:
120            self.module.fail_json(msg="Failed to find host system.")
121
122    def ensure(self):
123        """Manage Hyperthreading for an ESXi host system"""
124        results = dict(changed=False, result=dict())
125        desired_state = self.params.get('state')
126        host_change_list = []
127        for host in self.hosts:
128            changed = False
129            results['result'][host.name] = dict(msg='')
130
131            hyperthreading_info = host.config.hyperThread
132
133            results['result'][host.name]['state'] = desired_state
134            if desired_state == 'enabled':
135                # Don't do anything if Hyperthreading is already enabled
136                if hyperthreading_info.config:
137                    if hyperthreading_info.active:
138                        results['result'][host.name]['changed'] = False
139                        results['result'][host.name]['state_current'] = "active"
140                        results['result'][host.name]['msg'] = "Hyperthreading is enabled and active"
141                    if not hyperthreading_info.active:
142                        # L1 Terminal Fault (L1TF)/Foreshadow mitigation workaround (https://kb.vmware.com/s/article/55806)
143                        option_manager = host.configManager.advancedOption
144                        try:
145                            mitigation = option_manager.QueryOptions('VMkernel.Boot.hyperthreadingMitigation')
146                        except vim.fault.InvalidName:
147                            mitigation = None
148                        if mitigation and mitigation[0].value:
149                            results['result'][host.name]['changed'] = False
150                            results['result'][host.name]['state_current'] = "enabled"
151                            results['result'][host.name]['msg'] = ("Hyperthreading is enabled, but not active because the"
152                                                                   " processor is vulnerable to L1 Terminal Fault (L1TF).")
153                        else:
154                            changed = results['result'][host.name]['changed'] = True
155                            results['result'][host.name]['state_current'] = "enabled"
156                            results['result'][host.name]['msg'] = ("Hyperthreading is enabled, but not active."
157                                                                   " A reboot is required!")
158                # Enable Hyperthreading
159                else:
160                    # Check if Hyperthreading is available
161                    if hyperthreading_info.available:
162                        if not self.module.check_mode:
163                            try:
164                                host.configManager.cpuScheduler.EnableHyperThreading()
165                                changed = results['result'][host.name]['changed'] = True
166                                results['result'][host.name]['state_previous'] = "disabled"
167                                results['result'][host.name]['state_current'] = "enabled"
168                                results['result'][host.name]['msg'] = (
169                                    "Hyperthreading enabled for host. Reboot the host to activate it."
170                                )
171                            except vmodl.fault.NotSupported as not_supported:
172                                # This should never happen since Hyperthreading is available
173                                self.module.fail_json(
174                                    msg="Failed to enable Hyperthreading for host '%s' : %s" %
175                                    (host.name, to_native(not_supported.msg))
176                                )
177                            except (vmodl.RuntimeFault, vmodl.MethodFault) as runtime_fault:
178                                self.module.fail_json(
179                                    msg="Failed to enable Hyperthreading for host '%s' due to : %s" %
180                                    (host.name, to_native(runtime_fault.msg))
181                                )
182                        else:
183                            changed = results['result'][host.name]['changed'] = True
184                            results['result'][host.name]['state_previous'] = "disabled"
185                            results['result'][host.name]['state_current'] = "enabled"
186                            results['result'][host.name]['msg'] = "Hyperthreading will be enabled"
187                    else:
188                        self.module.fail_json(msg="Hyperthreading optimization is not available for host '%s'" % host.name)
189            elif desired_state == 'disabled':
190                # Don't do anything if Hyperthreading is already disabled
191                if not hyperthreading_info.config:
192                    if not hyperthreading_info.active:
193                        results['result'][host.name]['changed'] = False
194                        results['result'][host.name]['state_current'] = "inactive"
195                        results['result'][host.name]['msg'] = "Hyperthreading is disabled and inactive"
196                    if hyperthreading_info.active:
197                        changed = results['result'][host.name]['changed'] = True
198                        results['result'][host.name]['state_current'] = "disabled"
199                        results['result'][host.name]['msg'] = ("Hyperthreading is already disabled"
200                                                               " but still active. A reboot is required!")
201                # Disable Hyperthreading
202                else:
203                    # Check if Hyperthreading is available
204                    if hyperthreading_info.available:
205                        if not self.module.check_mode:
206                            try:
207                                host.configManager.cpuScheduler.DisableHyperThreading()
208                                changed = results['result'][host.name]['changed'] = True
209                                results['result'][host.name]['state_previous'] = "enabled"
210                                results['result'][host.name]['state_current'] = "disabled"
211                                results['result'][host.name]['msg'] = (
212                                    "Hyperthreading disabled. Reboot the host to deactivate it."
213                                )
214                            except vmodl.fault.NotSupported as not_supported:
215                                # This should never happen since Hyperthreading is available
216                                self.module.fail_json(
217                                    msg="Failed to disable Hyperthreading for host '%s' : %s" %
218                                    (host.name, to_native(not_supported.msg))
219                                )
220                            except (vmodl.RuntimeFault, vmodl.MethodFault) as runtime_fault:
221                                self.module.fail_json(
222                                    msg="Failed to disable Hyperthreading for host '%s' due to : %s" %
223                                    (host.name, to_native(runtime_fault.msg))
224                                )
225                        else:
226                            changed = results['result'][host.name]['changed'] = True
227                            results['result'][host.name]['state_previous'] = "enabled"
228                            results['result'][host.name]['state_current'] = "disabled"
229                            results['result'][host.name]['msg'] = "Hyperthreading will be disabled"
230                    else:
231                        self.module.fail_json(msg="Hyperthreading optimization is not available for host '%s'" % host.name)
232
233            host_change_list.append(changed)
234
235        if any(host_change_list):
236            results['changed'] = True
237        self.module.exit_json(**results)
238
239
240def main():
241    """Main"""
242    argument_spec = vmware_argument_spec()
243    argument_spec.update(
244        state=dict(default='enabled', choices=['enabled', 'disabled']),
245        esxi_hostname=dict(type='str', required=False),
246        cluster_name=dict(type='str', required=False),
247    )
248
249    module = AnsibleModule(argument_spec=argument_spec,
250                           required_one_of=[
251                               ['cluster_name', 'esxi_hostname'],
252                           ],
253                           supports_check_mode=True
254                           )
255
256    hyperthreading = VmwareHostHyperthreading(module)
257    hyperthreading.ensure()
258
259
260if __name__ == '__main__':
261    main()
262