#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2015, Joseph Callen # Copyright: (c) 2017-18, Ansible Project # Copyright: (c) 2017-18, 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 __metaclass__ = type ANSIBLE_METADATA = { 'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community' } DOCUMENTATION = ''' --- module: vmware_vmkernel short_description: Manages a VMware VMkernel Adapter of an ESXi host. description: - This module can be used to manage the VMKernel adapters / VMKernel network interfaces of an ESXi host. - The module assumes that the host is already configured with the Port Group in case of a vSphere Standard Switch (vSS). - The module assumes that the host is already configured with the Distributed Port Group in case of a vSphere Distributed Switch (vDS). - The module automatically migrates the VMKernel adapter from vSS to vDS or vice versa if present. version_added: 2.0 author: - Joseph Callen (@jcpowermac) - Russell Teague (@mtnbikenc) - Abhijeet Kasurde (@Akasurde) - Christian Kotte (@ckotte) notes: - The option C(device) need to be used with DHCP because otherwise it's not possible to check if a VMkernel device is already present - You can only change from DHCP to static, and vSS to vDS, or vice versa, in one step, without creating a new device, with C(device) specified. - You can only create the VMKernel adapter on a vDS if authenticated to vCenter and not if authenticated to ESXi. - Tested on vSphere 5.5 and 6.5 requirements: - "python >= 2.6" - PyVmomi options: vswitch_name: description: - The name of the vSwitch where to add the VMKernel interface. - Required parameter only if C(state) is set to C(present). - Optional parameter from version 2.5 and onwards. type: str aliases: ['vswitch'] dvswitch_name: description: - The name of the vSphere Distributed Switch (vDS) where to add the VMKernel interface. - Required parameter only if C(state) is set to C(present). - Optional parameter from version 2.8 and onwards. type: str aliases: ['dvswitch'] version_added: 2.8 portgroup_name: description: - The name of the port group for the VMKernel interface. required: True aliases: ['portgroup'] network: description: - A dictionary of network details. - 'The following parameter is required:' - ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)).' - 'The following parameters are required in case of C(type) is set to C(static):' - ' - C(ip_address) (string): Static IP address (implies C(type: static)).' - ' - C(subnet_mask) (string): Static netmask required for C(ip_address).' - 'The following parameter is optional in case of C(type) is set to C(static):' - ' - C(default_gateway) (string): Default gateway (Override default gateway for this adapter).' - 'The following parameter is optional:' - ' - C(tcpip_stack) (string): The TCP/IP stack for the VMKernel interface. Can be default, provisioning, vmotion, or vxlan. (default: default)' type: dict default: { type: 'static', tcpip_stack: 'default', } version_added: 2.5 ip_address: description: - The IP Address for the VMKernel interface. - Use C(network) parameter with C(ip_address) instead. - Deprecated option, will be removed in version 2.9. subnet_mask: description: - The Subnet Mask for the VMKernel interface. - Use C(network) parameter with C(subnet_mask) instead. - Deprecated option, will be removed in version 2.9. mtu: description: - The MTU for the VMKernel interface. - The default value of 1500 is valid from version 2.5 and onwards. default: 1500 device: description: - Search VMkernel adapter by device name. - The parameter is required only in case of C(type) is set to C(dhcp). version_added: 2.8 enable_vsan: description: - Enable VSAN traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool enable_vmotion: description: - Enable vMotion traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. - You cannot enable vMotion on an additional adapter if you already have an adapter with the vMotion TCP/IP stack configured. type: bool enable_mgmt: description: - Enable Management traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool enable_ft: description: - Enable Fault Tolerance traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool enable_provisioning: description: - Enable Provisioning traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool version_added: 2.8 enable_replication: description: - Enable vSphere Replication traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool version_added: 2.8 enable_replication_nfc: description: - Enable vSphere Replication NFC traffic on the VMKernel adapter. - This option is only allowed if the default TCP/IP stack is used. type: bool version_added: 2.8 state: description: - If set to C(present), the VMKernel adapter will be created with the given specifications. - If set to C(absent), the VMKernel adapter will be removed. - If set to C(present) and VMKernel adapter exists, the configurations will be updated. choices: [ present, absent ] default: present version_added: 2.5 esxi_hostname: description: - Name of ESXi host to which VMKernel is to be managed. - "From version 2.5 onwards, this parameter is required." required: True version_added: 2.5 extends_documentation_fragment: vmware.documentation ''' EXAMPLES = ''' - name: Add Management vmkernel port using static network type vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0001 network: type: 'static' ip_address: 192.168.127.10 subnet_mask: 255.255.255.0 state: present enable_mgmt: True delegate_to: localhost - name: Add Management vmkernel port using DHCP network type vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0002 state: present network: type: 'dhcp' enable_mgmt: True delegate_to: localhost - name: Change IP allocation from static to dhcp vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0002 state: present device: vmk1 network: type: 'dhcp' enable_mgmt: True delegate_to: localhost - name: Delete VMkernel port vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0002 state: absent delegate_to: localhost - name: Add Management vmkernel port to Distributed Switch vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' dvswitch_name: dvSwitch1 portgroup_name: dvPG_0001 network: type: 'static' ip_address: 192.168.127.10 subnet_mask: 255.255.255.0 state: present enable_mgmt: True delegate_to: localhost - name: Add vMotion vmkernel port with vMotion TCP/IP stack vmware_vmkernel: hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' esxi_hostname: '{{ esxi_hostname }}' dvswitch_name: dvSwitch1 portgroup_name: dvPG_0001 network: type: 'static' ip_address: 192.168.127.10 subnet_mask: 255.255.255.0 tcpip_stack: vmotion state: present delegate_to: localhost ''' RETURN = r''' result: description: metadata about VMKernel name returned: always type: dict sample: { "changed": false, "msg": "VMkernel Adapter already configured properly", "device": "vmk1", "ipv4": "static", "ipv4_gw": "No override", "ipv4_ip": "192.168.1.15", "ipv4_sm": "255.255.255.0", "mtu": 9000, "services": "vMotion", "switch": "vDS" } ''' try: from pyVmomi import vim, vmodl except ImportError: pass from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.vmware import ( PyVmomi, TaskError, vmware_argument_spec, wait_for_task, find_dvspg_by_name, find_dvs_by_name, get_all_objs ) from ansible.module_utils._text import to_native class PyVmomiHelper(PyVmomi): """Class to manage VMkernel configuration of an ESXi host system""" def __init__(self, module): super(PyVmomiHelper, self).__init__(module) if self.params['network']: self.network_type = self.params['network'].get('type') self.ip_address = self.params['network'].get('ip_address', None) self.subnet_mask = self.params['network'].get('subnet_mask', None) self.default_gateway = self.params['network'].get('default_gateway', None) if self.network_type == 'static': if not self.ip_address: module.fail_json(msg="ip_address is a required parameter when network type is set to 'static'") if not self.subnet_mask: module.fail_json(msg="subnet_mask is a required parameter when network type is set to 'static'") self.tcpip_stack = self.params['network'].get('tcpip_stack') else: self.network_type = 'dhcp' self.ip_address = None self.subnet_mask = None self.default_gateway = None self.tcpip_stack = 'default' self.device = self.params['device'] if self.network_type == 'dhcp' and not self.device: module.fail_json(msg="device is a required parameter when network type is set to 'dhcp'") self.mtu = self.params['mtu'] self.enable_vsan = self.params['enable_vsan'] self.enable_vmotion = self.params['enable_vmotion'] self.enable_mgmt = self.params['enable_mgmt'] self.enable_ft = self.params['enable_ft'] self.enable_provisioning = self.params['enable_provisioning'] self.enable_replication = self.params['enable_replication'] self.enable_replication_nfc = self.params['enable_replication_nfc'] self.vswitch_name = self.params['vswitch_name'] self.vds_name = self.params['dvswitch_name'] self.port_group_name = self.params['portgroup_name'] self.esxi_host_name = self.params['esxi_hostname'] hosts = self.get_all_host_objs(esxi_host_name=self.esxi_host_name) if hosts: self.esxi_host_obj = hosts[0] else: self.module.fail_json( msg="Failed to get details of ESXi server. Please specify esxi_hostname." ) # find Port Group if self.vswitch_name: self.port_group_obj = self.get_port_group_by_name( host_system=self.esxi_host_obj, portgroup_name=self.port_group_name, vswitch_name=self.vswitch_name ) if not self.port_group_obj: module.fail_json(msg="Portgroup '%s' not found on vSS '%s'" % (self.port_group_name, self.vswitch_name)) elif self.vds_name: self.dv_switch_obj = find_dvs_by_name(self.content, self.vds_name) if not self.dv_switch_obj: module.fail_json(msg="vDS '%s' not found" % self.vds_name) self.port_group_obj = find_dvspg_by_name(self.dv_switch_obj, self.port_group_name) if not self.port_group_obj: module.fail_json(msg="Portgroup '%s' not found on vDS '%s'" % (self.port_group_name, self.vds_name)) # find VMkernel Adapter if self.device: self.vnic = self.get_vmkernel_by_device(device_name=self.device) else: # config change (e.g. DHCP to static, or vice versa); doesn't work with virtual port change self.vnic = self.get_vmkernel_by_portgroup_new(port_group_name=self.port_group_name) if not self.vnic: if self.network_type == 'static': # vDS to vSS or vSS to vSS (static IP) self.vnic = self.get_vmkernel_by_ip(ip_address=self.ip_address) elif self.network_type == 'dhcp': # vDS to vSS or vSS to vSS (DHCP) self.vnic = self.get_vmkernel_by_device(device_name=self.device) def get_port_group_by_name(self, host_system, portgroup_name, vswitch_name): """ Get specific port group by given name Args: host_system: Name of Host System portgroup_name: Name of Port Group vswitch_name: Name of the vSwitch Returns: List of port groups by given specifications """ portgroups = self.get_all_port_groups_by_host(host_system=host_system) for portgroup in portgroups: if portgroup.spec.vswitchName == vswitch_name and portgroup.spec.name == portgroup_name: return portgroup return None def ensure(self): """ Manage internal VMKernel management Returns: NA """ host_vmk_states = { 'absent': { 'present': self.host_vmk_delete, 'absent': self.host_vmk_unchange, }, 'present': { 'present': self.host_vmk_update, 'absent': self.host_vmk_create, } } try: host_vmk_states[self.module.params['state']][self.check_state()]() except vmodl.RuntimeFault as runtime_fault: self.module.fail_json(msg=to_native(runtime_fault.msg)) except vmodl.MethodFault as method_fault: self.module.fail_json(msg=to_native(method_fault.msg)) def get_vmkernel_by_portgroup_new(self, port_group_name=None): """ Check if vmkernel available or not Args: port_group_name: name of port group Returns: vmkernel managed object if vmkernel found, false if not """ for vnic in self.esxi_host_obj.config.network.vnic: # check if it's a vSS Port Group if vnic.spec.portgroup == port_group_name: return vnic # check if it's a vDS Port Group try: if vnic.spec.distributedVirtualPort.portgroupKey == self.port_group_obj.key: return vnic except AttributeError: pass return False def get_vmkernel_by_ip(self, ip_address): """ Check if vmkernel available or not Args: ip_address: IP address of vmkernel device Returns: vmkernel managed object if vmkernel found, false if not """ vnics = [vnic for vnic in self.esxi_host_obj.config.network.vnic if vnic.spec.ip.ipAddress == ip_address] if vnics: return vnics[0] return None def get_vmkernel_by_device(self, device_name): """ Check if vmkernel available or not Args: device_name: name of vmkernel device Returns: vmkernel managed object if vmkernel found, false if not """ vnics = [vnic for vnic in self.esxi_host_obj.config.network.vnic if vnic.device == device_name] if vnics: return vnics[0] return None def check_state(self): """ Check internal state management Returns: Present if found and absent if not found """ state = 'absent' if self.vnic: state = 'present' return state def host_vmk_delete(self): """ Delete VMKernel Returns: NA """ results = dict(changed=False, msg='') vmk_device = self.vnic.device try: if self.module.check_mode: results['msg'] = "VMkernel Adapter would be deleted" else: self.esxi_host_obj.configManager.networkSystem.RemoveVirtualNic(vmk_device) results['msg'] = "VMkernel Adapter deleted" results['changed'] = True results['device'] = vmk_device except vim.fault.NotFound as not_found: self.module.fail_json( msg="Failed to find vmk to delete due to %s" % to_native(not_found.msg) ) except vim.fault.HostConfigFault as host_config_fault: self.module.fail_json( msg="Failed to delete vmk due host config issues : %s" % to_native(host_config_fault.msg) ) self.module.exit_json(**results) def host_vmk_unchange(self): """ Denote no change in VMKernel Returns: NA """ self.module.exit_json(changed=False) def host_vmk_update(self): """ Function to update VMKernel with given parameters Returns: NA """ changed = changed_settings = changed_vds = changed_services = \ changed_service_vmotion = changed_service_mgmt = changed_service_ft = \ changed_service_vsan = changed_service_prov = changed_service_rep = changed_service_rep_nfc = False changed_list = [] results = dict(changed=False, msg='') results['tcpip_stack'] = self.tcpip_stack net_stack_instance_key = self.get_api_net_stack_instance(self.tcpip_stack) if self.vnic.spec.netStackInstanceKey != net_stack_instance_key: self.module.fail_json(msg="The TCP/IP stack cannot be changed on an existing VMkernel adapter!") # Check MTU results['mtu'] = self.mtu if self.vnic.spec.mtu != self.mtu: changed_settings = True changed_list.append("MTU") results['mtu_previous'] = self.vnic.spec.mtu # Check IPv4 settings results['ipv4'] = self.network_type results['ipv4_ip'] = self.ip_address results['ipv4_sm'] = self.subnet_mask if self.default_gateway: results['ipv4_gw'] = self.default_gateway else: results['ipv4_gw'] = "No override" if self.vnic.spec.ip.dhcp: if self.network_type == 'static': changed_settings = True changed_list.append("IPv4 settings") results['ipv4_previous'] = "DHCP" if not self.vnic.spec.ip.dhcp: if self.network_type == 'dhcp': changed_settings = True changed_list.append("IPv4 settings") results['ipv4_previous'] = "static" elif self.network_type == 'static': if self.ip_address != self.vnic.spec.ip.ipAddress: changed_settings = True changed_list.append("IP") results['ipv4_ip_previous'] = self.vnic.spec.ip.ipAddress if self.subnet_mask != self.vnic.spec.ip.subnetMask: changed_settings = True changed_list.append("SM") results['ipv4_sm_previous'] = self.vnic.spec.ip.subnetMask if self.default_gateway: try: if self.default_gateway != self.vnic.spec.ipRouteSpec.ipRouteConfig.defaultGateway: changed_settings = True changed_list.append("GW override") results['ipv4_gw_previous'] = self.vnic.spec.ipRouteSpec.ipRouteConfig.defaultGateway except AttributeError: changed_settings = True changed_list.append("GW override") results['ipv4_gw_previous'] = "No override" else: try: if self.vnic.spec.ipRouteSpec.ipRouteConfig.defaultGateway: changed_settings = True changed_list.append("GW override") results['ipv4_gw_previous'] = self.vnic.spec.ipRouteSpec.ipRouteConfig.defaultGateway except AttributeError: pass # Check virtual port (vSS or vDS) results['portgroup'] = self.port_group_name dvs_uuid = None if self.vswitch_name: results['switch'] = self.vswitch_name try: if self.vnic.spec.distributedVirtualPort.switchUuid: changed_vds = True changed_list.append("Virtual Port") dvs_uuid = self.vnic.spec.distributedVirtualPort.switchUuid except AttributeError: pass if changed_vds: results['switch_previous'] = self.find_dvs_by_uuid(dvs_uuid) self.dv_switch_obj = find_dvs_by_name(self.content, results['switch_previous']) results['portgroup_previous'] = self.find_dvspg_by_key( self.dv_switch_obj, self.vnic.spec.distributedVirtualPort.portgroupKey ) elif self.vds_name: results['switch'] = self.vds_name try: if self.vnic.spec.distributedVirtualPort.switchUuid != self.dv_switch_obj.uuid: changed_vds = True changed_list.append("Virtual Port") dvs_uuid = self.vnic.spec.distributedVirtualPort.switchUuid except AttributeError: changed_vds = True changed_list.append("Virtual Port") if changed_vds: results['switch_previous'] = self.find_dvs_by_uuid(dvs_uuid) results['portgroup_previous'] = self.vnic.spec.portgroup portgroups = self.get_all_port_groups_by_host(host_system=self.esxi_host_obj) for portgroup in portgroups: if portgroup.spec.name == self.vnic.spec.portgroup: results['switch_previous'] = portgroup.spec.vswitchName results['services'] = self.create_enabled_services_string() # Check configuration of service types (only if default TCP/IP stack is used) if self.vnic.spec.netStackInstanceKey == 'defaultTcpipStack': service_type_vmks = self.get_all_vmks_by_service_type() if (self.enable_vmotion and self.vnic.device not in service_type_vmks['vmotion']) or \ (not self.enable_vmotion and self.vnic.device in service_type_vmks['vmotion']): changed_services = changed_service_vmotion = True if (self.enable_mgmt and self.vnic.device not in service_type_vmks['management']) or \ (not self.enable_mgmt and self.vnic.device in service_type_vmks['management']): changed_services = changed_service_mgmt = True if (self.enable_ft and self.vnic.device not in service_type_vmks['faultToleranceLogging']) or \ (not self.enable_ft and self.vnic.device in service_type_vmks['faultToleranceLogging']): changed_services = changed_service_ft = True if (self.enable_vsan and self.vnic.device not in service_type_vmks['vsan']) or \ (not self.enable_vsan and self.vnic.device in service_type_vmks['vsan']): changed_services = changed_service_vsan = True if (self.enable_provisioning and self.vnic.device not in service_type_vmks['vSphereProvisioning']) or \ (not self.enable_provisioning and self.vnic.device in service_type_vmks['vSphereProvisioning']): changed_services = changed_service_prov = True if (self.enable_replication and self.vnic.device not in service_type_vmks['vSphereReplication']) or \ (not self.enable_provisioning and self.vnic.device in service_type_vmks['vSphereReplication']): changed_services = changed_service_rep = True if (self.enable_replication_nfc and self.vnic.device not in service_type_vmks['vSphereReplicationNFC']) or \ (not self.enable_provisioning and self.vnic.device in service_type_vmks['vSphereReplicationNFC']): changed_services = changed_service_rep_nfc = True if changed_services: changed_list.append("services") if changed_settings or changed_vds or changed_services: changed = True if self.module.check_mode: changed_suffix = ' would be updated' else: changed_suffix = ' updated' if len(changed_list) > 2: message = ', '.join(changed_list[:-1]) + ', and ' + str(changed_list[-1]) elif len(changed_list) == 2: message = ' and '.join(changed_list) elif len(changed_list) == 1: message = changed_list[0] message = "VMkernel Adapter " + message + changed_suffix if changed_settings or changed_vds: vnic_config = vim.host.VirtualNic.Specification() ip_spec = vim.host.IpConfig() if self.network_type == 'dhcp': ip_spec.dhcp = True else: ip_spec.dhcp = False ip_spec.ipAddress = self.ip_address ip_spec.subnetMask = self.subnet_mask if self.default_gateway: vnic_config.ipRouteSpec = vim.host.VirtualNic.IpRouteSpec() vnic_config.ipRouteSpec.ipRouteConfig = vim.host.IpRouteConfig() vnic_config.ipRouteSpec.ipRouteConfig.defaultGateway = self.default_gateway else: vnic_config.ipRouteSpec = vim.host.VirtualNic.IpRouteSpec() vnic_config.ipRouteSpec.ipRouteConfig = vim.host.IpRouteConfig() vnic_config.ip = ip_spec vnic_config.mtu = self.mtu if changed_vds: if self.vswitch_name: vnic_config.portgroup = self.port_group_name elif self.vds_name: vnic_config.distributedVirtualPort = vim.dvs.PortConnection() vnic_config.distributedVirtualPort.switchUuid = self.dv_switch_obj.uuid vnic_config.distributedVirtualPort.portgroupKey = self.port_group_obj.key try: if not self.module.check_mode: self.esxi_host_obj.configManager.networkSystem.UpdateVirtualNic(self.vnic.device, vnic_config) except vim.fault.NotFound as not_found: self.module.fail_json( msg="Failed to update vmk as virtual network adapter cannot be found %s" % to_native(not_found.msg) ) except vim.fault.HostConfigFault as host_config_fault: self.module.fail_json( msg="Failed to update vmk due to host config issues : %s" % to_native(host_config_fault.msg) ) except vim.fault.InvalidState as invalid_state: self.module.fail_json( msg="Failed to update vmk as ipv6 address is specified in an ipv4 only system : %s" % to_native(invalid_state.msg) ) except vmodl.fault.InvalidArgument as invalid_arg: self.module.fail_json( msg="Failed to update vmk as IP address or Subnet Mask in the IP configuration" "are invalid or PortGroup does not exist : %s" % to_native(invalid_arg.msg) ) if changed_services: changed_list.append("Services") services_previous = [] vnic_manager = self.esxi_host_obj.configManager.virtualNicManager if changed_service_mgmt: if self.vnic.device in service_type_vmks['management']: services_previous.append('Mgmt') operation = 'select' if self.enable_mgmt else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='management', operation=operation ) if changed_service_vmotion: if self.vnic.device in service_type_vmks['vmotion']: services_previous.append('vMotion') operation = 'select' if self.enable_vmotion else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='vmotion', operation=operation ) if changed_service_ft: if self.vnic.device in service_type_vmks['faultToleranceLogging']: services_previous.append('FT') operation = 'select' if self.enable_ft else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='faultToleranceLogging', operation=operation ) if changed_service_prov: if self.vnic.device in service_type_vmks['vSphereProvisioning']: services_previous.append('Prov') operation = 'select' if self.enable_provisioning else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereProvisioning', operation=operation ) if changed_service_rep: if self.vnic.device in service_type_vmks['vSphereReplication']: services_previous.append('Repl') operation = 'select' if self.enable_replication else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplication', operation=operation ) if changed_service_rep_nfc: if self.vnic.device in service_type_vmks['vSphereReplicationNFC']: services_previous.append('Repl_NFC') operation = 'select' if self.enable_replication_nfc else 'deselect' self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplicationNFC', operation=operation ) if changed_service_vsan: if self.vnic.device in service_type_vmks['vsan']: services_previous.append('VSAN') if self.enable_vsan: results['vsan'] = self.set_vsan_service_type() else: self.set_service_type( vnic_manager=vnic_manager, vmk=self.vnic, service_type='vsan', operation=operation ) results['services_previous'] = ', '.join(services_previous) else: message = "VMkernel Adapter already configured properly" results['changed'] = changed results['msg'] = message results['device'] = self.vnic.device self.module.exit_json(**results) def find_dvs_by_uuid(self, uuid): """ Find DVS by UUID Returns: DVS name """ dvs_list = get_all_objs(self.content, [vim.DistributedVirtualSwitch]) for dvs in dvs_list: if dvs.uuid == uuid: return dvs.summary.name return None def find_dvspg_by_key(self, dv_switch, portgroup_key): """ Find dvPortgroup by key Returns: dvPortgroup name """ portgroups = dv_switch.portgroup for portgroup in portgroups: if portgroup.key == portgroup_key: return portgroup.name return None def set_vsan_service_type(self): """ Set VSAN service type Returns: result of UpdateVsan_Task """ result = None vsan_system = self.esxi_host_obj.configManager.vsanSystem vsan_port_config = vim.vsan.host.ConfigInfo.NetworkInfo.PortConfig() vsan_port_config.device = self.vnic.device vsan_config = vim.vsan.host.ConfigInfo() vsan_config.networkInfo = vim.vsan.host.ConfigInfo.NetworkInfo() vsan_config.networkInfo.port = [vsan_port_config] if not self.module.check_mode: try: vsan_task = vsan_system.UpdateVsan_Task(vsan_config) wait_for_task(vsan_task) except TaskError as task_err: self.module.fail_json( msg="Failed to set service type to vsan for %s : %s" % (self.vnic.device, to_native(task_err)) ) return result def host_vmk_create(self): """ Create VMKernel Returns: NA """ results = dict(changed=False, message='') if self.vswitch_name: results['switch'] = self.vswitch_name elif self.vds_name: results['switch'] = self.vds_name results['portgroup'] = self.port_group_name vnic_config = vim.host.VirtualNic.Specification() ip_spec = vim.host.IpConfig() results['ipv4'] = self.network_type if self.network_type == 'dhcp': ip_spec.dhcp = True else: ip_spec.dhcp = False results['ipv4_ip'] = self.ip_address results['ipv4_sm'] = self.subnet_mask ip_spec.ipAddress = self.ip_address ip_spec.subnetMask = self.subnet_mask if self.default_gateway: vnic_config.ipRouteSpec = vim.host.VirtualNic.IpRouteSpec() vnic_config.ipRouteSpec.ipRouteConfig = vim.host.IpRouteConfig() vnic_config.ipRouteSpec.ipRouteConfig.defaultGateway = self.default_gateway vnic_config.ip = ip_spec results['mtu'] = self.mtu vnic_config.mtu = self.mtu results['tcpip_stack'] = self.tcpip_stack vnic_config.netStackInstanceKey = self.get_api_net_stack_instance(self.tcpip_stack) vmk_device = None try: if self.module.check_mode: results['msg'] = "VMkernel Adapter would be created" else: if self.vswitch_name: vmk_device = self.esxi_host_obj.configManager.networkSystem.AddVirtualNic( self.port_group_name, vnic_config ) elif self.vds_name: vnic_config.distributedVirtualPort = vim.dvs.PortConnection() vnic_config.distributedVirtualPort.switchUuid = self.dv_switch_obj.uuid vnic_config.distributedVirtualPort.portgroupKey = self.port_group_obj.key vmk_device = self.esxi_host_obj.configManager.networkSystem.AddVirtualNic(portgroup="", nic=vnic_config) results['msg'] = "VMkernel Adapter created" results['changed'] = True results['device'] = vmk_device if self.network_type != 'dhcp': if self.default_gateway: results['ipv4_gw'] = self.default_gateway else: results['ipv4_gw'] = "No override" results['services'] = self.create_enabled_services_string() except vim.fault.AlreadyExists as already_exists: self.module.fail_json( msg="Failed to add vmk as portgroup already has a virtual network adapter %s" % to_native(already_exists.msg) ) except vim.fault.HostConfigFault as host_config_fault: self.module.fail_json( msg="Failed to add vmk due to host config issues : %s" % to_native(host_config_fault.msg) ) except vim.fault.InvalidState as invalid_state: self.module.fail_json( msg="Failed to add vmk as ipv6 address is specified in an ipv4 only system : %s" % to_native(invalid_state.msg) ) except vmodl.fault.InvalidArgument as invalid_arg: self.module.fail_json( msg="Failed to add vmk as IP address or Subnet Mask in the IP configuration " "are invalid or PortGroup does not exist : %s" % to_native(invalid_arg.msg) ) # do service type configuration if self.tcpip_stack == 'default' and not all( option is False for option in [self.enable_vsan, self.enable_vmotion, self.enable_mgmt, self.enable_ft, self.enable_provisioning, self.enable_replication, self.enable_replication_nfc]): self.vnic = self.get_vmkernel_by_device(device_name=vmk_device) # VSAN if self.enable_vsan: results['vsan'] = self.set_vsan_service_type() # Other service type host_vnic_manager = self.esxi_host_obj.configManager.virtualNicManager if self.enable_vmotion: self.set_service_type(host_vnic_manager, self.vnic, 'vmotion') if self.enable_mgmt: self.set_service_type(host_vnic_manager, self.vnic, 'management') if self.enable_ft: self.set_service_type(host_vnic_manager, self.vnic, 'faultToleranceLogging') if self.enable_provisioning: self.set_service_type(host_vnic_manager, self.vnic, 'vSphereProvisioning') if self.enable_replication: self.set_service_type(host_vnic_manager, self.vnic, 'vSphereReplication') if self.enable_replication_nfc: self.set_service_type(host_vnic_manager, self.vnic, 'vSphereReplicationNFC') self.module.exit_json(**results) def set_service_type(self, vnic_manager, vmk, service_type, operation='select'): """ Set service type to given VMKernel Args: vnic_manager: Virtual NIC manager object vmk: VMkernel managed object service_type: Name of service type operation: Select to select service type, deselect to deselect service type """ try: if operation == 'select': if not self.module.check_mode: vnic_manager.SelectVnicForNicType(service_type, vmk.device) elif operation == 'deselect': if not self.module.check_mode: vnic_manager.DeselectVnicForNicType(service_type, vmk.device) except vmodl.fault.InvalidArgument as invalid_arg: self.module.fail_json( msg="Failed to %s VMK service type '%s' on '%s' due to : %s" % (operation, service_type, vmk.device, to_native(invalid_arg.msg)) ) def get_all_vmks_by_service_type(self): """ Return information about service types and VMKernel Returns: Dictionary of service type as key and VMKernel list as value """ service_type_vmk = dict( vmotion=[], vsan=[], management=[], faultToleranceLogging=[], vSphereProvisioning=[], vSphereReplication=[], vSphereReplicationNFC=[], ) for service_type in list(service_type_vmk): vmks_list = self.query_service_type_for_vmks(service_type) service_type_vmk[service_type] = vmks_list return service_type_vmk def query_service_type_for_vmks(self, service_type): """ Return list of VMKernels Args: service_type: Name of service type Returns: List of VMKernel which belongs to that service type """ vmks_list = [] query = None try: query = self.esxi_host_obj.configManager.virtualNicManager.QueryNetConfig(service_type) except vim.fault.HostConfigFault as config_fault: self.module.fail_json( msg="Failed to get all VMKs for service type %s due to host config fault : %s" % (service_type, to_native(config_fault.msg)) ) except vmodl.fault.InvalidArgument as invalid_argument: self.module.fail_json( msg="Failed to get all VMKs for service type %s due to invalid arguments : %s" % (service_type, to_native(invalid_argument.msg)) ) if not query.selectedVnic: return vmks_list selected_vnics = [vnic for vnic in query.selectedVnic] vnics_with_service_type = [vnic.device for vnic in query.candidateVnic if vnic.key in selected_vnics] return vnics_with_service_type def create_enabled_services_string(self): """Create services list""" services = [] if self.enable_mgmt: services.append('Mgmt') if self.enable_vmotion: services.append('vMotion') if self.enable_ft: services.append('FT') if self.enable_vsan: services.append('VSAN') if self.enable_provisioning: services.append('Prov') if self.enable_replication: services.append('Repl') if self.enable_replication_nfc: services.append('Repl_NFC') return ', '.join(services) @staticmethod def get_api_net_stack_instance(tcpip_stack): """Get TCP/IP stack instance name or key""" net_stack_instance = None if tcpip_stack == 'default': net_stack_instance = 'defaultTcpipStack' elif tcpip_stack == 'provisioning': net_stack_instance = 'vSphereProvisioning' # vmotion and vxlan stay the same elif tcpip_stack == 'vmotion': net_stack_instance = 'vmotion' elif tcpip_stack == 'vxlan': net_stack_instance = 'vxlan' elif tcpip_stack == 'defaultTcpipStack': net_stack_instance = 'default' elif tcpip_stack == 'vSphereProvisioning': net_stack_instance = 'provisioning' # vmotion and vxlan stay the same elif tcpip_stack == 'vmotion': net_stack_instance = 'vmotion' elif tcpip_stack == 'vxlan': net_stack_instance = 'vxlan' return net_stack_instance def main(): """Main""" argument_spec = vmware_argument_spec() argument_spec.update(dict( esxi_hostname=dict(required=True, type='str'), portgroup_name=dict(required=True, type='str', aliases=['portgroup']), ip_address=dict(removed_in_version=2.9, type='str'), subnet_mask=dict(removed_in_version=2.9, type='str'), mtu=dict(required=False, type='int', default=1500), device=dict(type='str'), enable_vsan=dict(required=False, type='bool', default=False), enable_vmotion=dict(required=False, type='bool', default=False), enable_mgmt=dict(required=False, type='bool', default=False), enable_ft=dict(required=False, type='bool', default=False), enable_provisioning=dict(type='bool', default=False), enable_replication=dict(type='bool', default=False), enable_replication_nfc=dict(type='bool', default=False), vswitch_name=dict(required=False, type='str', aliases=['vswitch']), dvswitch_name=dict(required=False, type='str', aliases=['dvswitch']), network=dict( type='dict', options=dict( type=dict(type='str', default='static', choices=['static', 'dhcp']), ip_address=dict(type='str'), subnet_mask=dict(type='str'), default_gateway=dict(type='str'), tcpip_stack=dict(type='str', default='default', choices=['default', 'provisioning', 'vmotion', 'vxlan']), ), default=dict( type='static', tcpip_stack='default', ), ), state=dict( type='str', default='present', choices=['absent', 'present'] ), )) module = AnsibleModule(argument_spec=argument_spec, mutually_exclusive=[ ['vswitch_name', 'dvswitch_name'], ['tcpip_stack', 'enable_vsan'], ['tcpip_stack', 'enable_vmotion'], ['tcpip_stack', 'enable_mgmt'], ['tcpip_stack', 'enable_ft'], ['tcpip_stack', 'enable_provisioning'], ['tcpip_stack', 'enable_replication'], ['tcpip_stack', 'enable_replication_nfc'], ], required_one_of=[ ['vswitch_name', 'dvswitch_name'], ['portgroup_name', 'device'], ], required_if=[ ['state', 'present', ['portgroup_name']], ['state', 'absent', ['device']] ], supports_check_mode=True) pyv = PyVmomiHelper(module) pyv.ensure() if __name__ == '__main__': main()