From e0f7a522a3ef8b538e0b699dfa016237aff282b3 Mon Sep 17 00:00:00 2001 From: Christian Kotte Date: Tue, 30 Oct 2018 07:51:50 +0100 Subject: [PATCH] VMware: Improve module vmware_host_vmnic_facts (#47278) * fix "AttributeError: 'NoneType' object has no attribute 'nicDevice'" * add vmnic_details output (speed, duplex, vendor, mac, etc. pp.) * add NIC capabilities, DirectPath I/O, and SR-IOV information output * add num_vmnics --- .../cloud/vmware/vmware_host_vmnic_facts.py | 155 ++++++++++++++++-- .../vmware_host_vmnic_facts/tasks/main.yml | 22 ++- 2 files changed, 165 insertions(+), 12 deletions(-) diff --git a/lib/ansible/modules/cloud/vmware/vmware_host_vmnic_facts.py b/lib/ansible/modules/cloud/vmware/vmware_host_vmnic_facts.py index 1dcd15f6668..f6f331ecd57 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_host_vmnic_facts.py +++ b/lib/ansible/modules/cloud/vmware/vmware_host_vmnic_facts.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2018, Abhijeet Kasurde +# Copyright: (c) 2018, Christian Kotte # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -26,22 +27,43 @@ description: version_added: '2.5' author: - Abhijeet Kasurde (@Akasurde) +- Christian Kotte (@ckotte) notes: - Tested on vSphere 6.5 requirements: - python >= 2.6 - PyVmomi options: - cluster_name: + capabilities: description: - - Name of the cluster. - - Vmnic facts about each ESXi server will be returned for the given cluster. - - If C(esxi_hostname) is not given, this parameter is required. + - Gather facts about general capabilities (Auto negotioation, Wake On LAN, and Network I/O Control). + type: bool + default: false + version_added: 2.8 + directpath_io: + description: + - Gather facts about DirectPath I/O capabilites and configuration. + type: bool + default: false + version_added: 2.8 + sriov: + description: + - Gather facts about SR-IOV capabilites and configuration. + type: bool + default: false + version_added: 2.8 esxi_hostname: description: - - ESXi hostname. + - Name of the host system to work with. - Vmnic facts about this ESXi server will be returned. - - If C(cluster_name) is not given, this parameter is required. + - This parameter is required if C(cluster_name) is not specified. + type: str + cluster_name: + description: + - Name of the cluster from which all host systems will be used. + - Vmnic facts about each ESXi server will be returned for the given cluster. + - This parameter is required if C(esxi_hostname) is not specified. + type: str extends_documentation_fragment: vmware.documentation ''' @@ -69,7 +91,9 @@ RETURN = r''' hosts_vmnics_facts: description: - dict with hostname as key and dict with vmnics facts as value. - - details about vswitch and dvswitch is added in version 2.7. + - for C(num_vmnics), only NICs starting with vmnic are counted. NICs like vusb* are not counted. + - details about vswitch and dvswitch was added in version 2.7. + - details about vmnics was added in version 2.8. returned: hosts_vmnics_facts type: dict sample: @@ -85,10 +109,37 @@ hosts_vmnics_facts: "vmnic1" ] }, + "num_vmnics": 2, "used": [ "vmnic1", "vmnic0" ], + "vmnic_details": [ + { + "actual_duplex": "Full Duplex", + "actual_speed": 10000, + "adapter": "Intel(R) 82599 10 Gigabit Dual Port Network Connection", + "configured_duplex": "Auto negotiate", + "configured_speed": "Auto negotiate", + "device": "vmnic0", + "driver": "ixgbe", + "location": "0000:01:00.0", + "mac": "aa:bb:cc:dd:ee:ff", + "status": "Connected", + }, + { + "actual_duplex": "Full Duplex", + "actual_speed": 10000, + "adapter": "Intel(R) 82599 10 Gigabit Dual Port Network Connection", + "configured_duplex": "Auto negotiate", + "configured_speed": "Auto negotiate", + "device": "vmnic1", + "driver": "ixgbe", + "location": "0000:01:00.1", + "mac": "ab:ba:cc:dd:ee:ff", + "status": "Connected", + }, + ], "vswitch": { "vSwitch0": [ "vmnic0" @@ -108,13 +159,20 @@ from ansible.module_utils.vmware import vmware_argument_spec, PyVmomi, get_all_o class HostVmnicMgr(PyVmomi): + """Class to manage vmnic facts""" def __init__(self, module): super(HostVmnicMgr, self).__init__(module) + self.capabilities = self.params.get('capabilities') + self.directpath_io = self.params.get('directpath_io') + self.sriov = self.params.get('sriov') cluster_name = self.params.get('cluster_name', None) esxi_host_name = self.params.get('esxi_hostname', None) self.hosts = self.get_all_host_objs(cluster_name=cluster_name, esxi_host_name=esxi_host_name) + if not self.hosts: + self.module.fail_json(msg="Failed to find host system.") def find_dvs_by_uuid(self, uuid=None): + """Find DVS by it's UUID""" dvs_obj = None if uuid is None: return dvs_obj @@ -128,6 +186,7 @@ class HostVmnicMgr(PyVmomi): return dvs_obj def gather_host_vmnic_facts(self): + """Gather vmnic facts""" hosts_vmnic_facts = {} for host in self.hosts: host_vmnic_facts = dict(all=[], available=[], used=[], vswitch=dict(), dvswitch=dict()) @@ -135,15 +194,87 @@ class HostVmnicMgr(PyVmomi): if host_nw_system: nw_config = host_nw_system.networkConfig host_vmnic_facts['all'] = [pnic.device for pnic in nw_config.pnic] + host_vmnic_facts['num_vmnics'] = ( + len(filter(lambda s: s.startswith('vmnic'), [pnic.device for pnic in nw_config.pnic])) + ) + host_vmnic_facts['vmnic_details'] = [] + for pnic in host.config.network.pnic: + pnic_facts = dict() + if pnic.device.startswith('vmnic'): + if pnic.pci: + pnic_facts['location'] = pnic.pci + for pci_device in host.hardware.pciDevice: + if pci_device.id == pnic.pci: + pnic_facts['adapter'] = pci_device.vendorName + ' ' + pci_device.deviceName + break + else: + pnic_facts['location'] = 'PCI' + pnic_facts['device'] = pnic.device + pnic_facts['driver'] = pnic.driver + if pnic.linkSpeed: + pnic_facts['status'] = 'Connected' + pnic_facts['actual_speed'] = pnic.linkSpeed.speedMb + pnic_facts['actual_duplex'] = 'Full Duplex' if pnic.linkSpeed.duplex else 'Half Duplex' + else: + pnic_facts['status'] = 'Disconnected' + pnic_facts['actual_speed'] = 'N/A' + pnic_facts['actual_duplex'] = 'N/A' + if pnic.spec.linkSpeed: + pnic_facts['configured_speed'] = pnic.spec.linkSpeed.speedMb + pnic_facts['configured_duplex'] = 'Full Duplex' if pnic.spec.linkSpeed.duplex else 'Half Duplex' + else: + pnic_facts['configured_speed'] = 'Auto negotiate' + pnic_facts['configured_duplex'] = 'Auto negotiate' + pnic_facts['mac'] = pnic.mac + # General NIC capabilities + if self.capabilities: + pnic_facts['nioc_status'] = 'Allowed' if pnic.resourcePoolSchedulerAllowed else 'Not allowed' + pnic_facts['auto_negotiation_supported'] = pnic.autoNegotiateSupported + pnic_facts['wake_on_lan_supported'] = pnic.wakeOnLanSupported + # DirectPath I/O and SR-IOV capabilities and configuration + if self.directpath_io: + pnic_facts['directpath_io_supported'] = pnic.vmDirectPathGen2Supported + if self.directpath_io or self.sriov: + if pnic.pci: + for pci_device in host.configManager.pciPassthruSystem.pciPassthruInfo: + if pci_device.id == pnic.pci: + if self.directpath_io: + pnic_facts['passthru_enabled'] = pci_device.passthruEnabled + pnic_facts['passthru_capable'] = pci_device.passthruCapable + pnic_facts['passthru_active'] = pci_device.passthruActive + if self.sriov: + try: + if pci_device.sriovCapable: + pnic_facts['sriov_status'] = ( + 'Enabled' if pci_device.sriovEnabled else 'Disabled' + ) + pnic_facts['sriov_active'] = \ + pci_device.sriovActive + pnic_facts['sriov_virt_functions'] = \ + pci_device.numVirtualFunction + pnic_facts['sriov_virt_functions_requested'] = \ + pci_device.numVirtualFunctionRequested + pnic_facts['sriov_virt_functions_supported'] = \ + pci_device.maxVirtualFunctionSupported + else: + pnic_facts['sriov_status'] = 'Not supported' + except AttributeError: + pnic_facts['sriov_status'] = 'Not supported' + host_vmnic_facts['vmnic_details'].append(pnic_facts) vswitch_vmnics = [] proxy_switch_vmnics = [] if nw_config.vswitch: for vswitch in nw_config.vswitch: host_vmnic_facts['vswitch'][vswitch.name] = [] - for vnic in vswitch.spec.bridge.nicDevice: - vswitch_vmnics.append(vnic) - host_vmnic_facts['vswitch'][vswitch.name].append(vnic) + # Workaround for "AttributeError: 'NoneType' object has no attribute 'nicDevice'" + # this issue doesn't happen every time; vswitch.spec.bridge.nicDevice exists! + try: + for vnic in vswitch.spec.bridge.nicDevice: + vswitch_vmnics.append(vnic) + host_vmnic_facts['vswitch'][vswitch.name].append(vnic) + except AttributeError: + pass if nw_config.proxySwitch: for proxy_config in nw_config.proxySwitch: @@ -164,10 +295,14 @@ class HostVmnicMgr(PyVmomi): def main(): + """Main""" argument_spec = vmware_argument_spec() argument_spec.update( cluster_name=dict(type='str', required=False), esxi_hostname=dict(type='str', required=False), + capabilities=dict(type='bool', required=False, default=False), + directpath_io=dict(type='bool', required=False, default=False), + sriov=dict(type='bool', required=False, default=False), ) module = AnsibleModule( diff --git a/test/integration/targets/vmware_host_vmnic_facts/tasks/main.yml b/test/integration/targets/vmware_host_vmnic_facts/tasks/main.yml index fb6cdbf607f..85999d1450f 100644 --- a/test/integration/targets/vmware_host_vmnic_facts/tasks/main.yml +++ b/test/integration/targets/vmware_host_vmnic_facts/tasks/main.yml @@ -45,7 +45,7 @@ - debug: var=host1 -- name: Gather facts about all hosts in given cluster +- name: Gather vmnic facts about a host vmware_host_vmnics_facts: hostname: "{{ vcsim }}" username: "{{ user }}" @@ -58,4 +58,22 @@ - assert: that: - - host_vmnics.host_service_facts is defined + - host_vmnics.hosts_vmnics_facts is defined + +- name: Gather extended vmnic facts about a host + vmware_host_vmnics_facts: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + validate_certs: no + capabilities: true + directpath_io: true + sriov: true + register: host_vmnics_extended + +- debug: var=host_vmnics_extended + +- assert: + that: + - host_vmnics_extended.hosts_vmnics_facts is defined