From bd0cad6ed718cb19ef9dac29f48137b5c7f5fde0 Mon Sep 17 00:00:00 2001 From: Christian Kotte Date: Wed, 20 Feb 2019 16:57:35 +0100 Subject: [PATCH] Improve vmware_vmkernel module (#47270) * update description and examples * show updated settings * add check mode support * Remove unused option `vlan_id` * add vDS support * add TCP/IP stack support --- .../modules/cloud/vmware/vmware_vmkernel.py | 1012 ++++++++++++----- .../targets/vmware_vmkernel/aliases | 2 + .../targets/vmware_vmkernel/tasks/main.yml | 144 +++ 3 files changed, 880 insertions(+), 278 deletions(-) create mode 100644 test/integration/targets/vmware_vmkernel/aliases create mode 100644 test/integration/targets/vmware_vmkernel/tasks/main.yml diff --git a/lib/ansible/modules/cloud/vmware/vmware_vmkernel.py b/lib/ansible/modules/cloud/vmware/vmware_vmkernel.py index 1a8bfb1ef6f..afd0444ea88 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_vmkernel.py +++ b/lib/ansible/modules/cloud/vmware/vmware_vmkernel.py @@ -4,6 +4,7 @@ # 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 @@ -18,17 +19,23 @@ ANSIBLE_METADATA = { DOCUMENTATION = ''' --- module: vmware_vmkernel -short_description: Manage a VMware VMkernel Interface aka. Virtual NICs of host system. +short_description: Manages a VMware VMkernel Adapter of an ESXi host. description: - - 'This module can be used to manage the VMWare VMKernel interface (also known as Virtual NICs) of host system.' - - This module assumes that the host is already configured with Portgroup and vSwitch. + - 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: - - Tested on vSphere 5.5, 6.5 + - 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 @@ -38,18 +45,38 @@ options: - 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. - - 'Following parameter is required:' + - 'The following parameter is required:' - ' - C(type) (string): Type of IP assignment (either C(dhcp) or C(static)).' - - 'Following parameters are required in case of C(type) is set to 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).' + - ' - 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: @@ -61,53 +88,60 @@ options: - 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. - vlan_id: - description: - - The VLAN ID for the VMKernel interface. - - Required parameter only if C(state) is set to C(present). - - Optional parameter from version 2.5 and onwards. - version_added: 2.0 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 the VMKernel interface for VSAN traffic. + - 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 the VMKernel interface for vMotion traffic. + - 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 the VMKernel interface for Management traffic. + - 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 the VMKernel interface for Fault Tolerance traffic. + - 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 the VMKernel interface for provisioning service. + - 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 the VMKernel interface for vSphere replication service. + - 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 the VMKernel interface for vSphere replication NFC service. + - 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), VMKernel is created with the given specifications. - - If set to C(absent), VMKernel is removed from the given configurations. - - If set to C(present) and VMKernel exists then VMKernel configurations are updated. + - 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 @@ -126,9 +160,9 @@ EXAMPLES = ''' hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' + esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0001 - vlan_id: '{{ vlan_id }}' network: type: 'static' ip_address: 192.168.127.10 @@ -142,25 +176,72 @@ EXAMPLES = ''' hostname: '{{ esxi_hostname }}' username: '{{ esxi_username }}' password: '{{ esxi_password }}' + esxi_hostname: '{{ esxi_hostname }}' vswitch_name: vSwitch0 portgroup_name: PG_0002 - vlan_id: '{{ vlan_id }}' state: present network: type: 'dhcp' enable_mgmt: True delegate_to: localhost -- name: Delete VMkernel port using DHCP network type +- 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 - vlan_id: '{{ vlan_id }}' 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''' @@ -169,7 +250,16 @@ result: returned: always type: dict sample: { - results : "vmk1" + "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" } ''' @@ -179,17 +269,38 @@ except ImportError: pass from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task +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) - self.port_group_name = self.params['portgroup_name'] - self.ip_address = self.params['network'].get('ip_address', None) - self.subnet_mask = self.params['network'].get('subnet_mask', None) - self.network_type = self.params['network']['type'] + 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'] @@ -200,43 +311,66 @@ class PyVmomiHelper(PyVmomi): self.enable_replication_nfc = self.params['enable_replication_nfc'] self.vswitch_name = self.params['vswitch_name'] - self.vlan_id = self.params['vlan_id'] + 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("Failed to get details of ESXi server." - " Please specify esxi_hostname.") - - self.port_group_obj = self.get_port_group_by_name(host_system=self.esxi_host_obj, portgroup_name=self.port_group_name) - if not self.port_group_obj: - module.fail_json(msg="Portgroup name %s not found" % self.port_group_name) - - if self.network_type == 'static': - if not self.ip_address: - module.fail_json(msg="network.ip_address is required parameter when network is set to 'static'.") - if not self.subnet_mask: - module.fail_json(msg="network.subnet_mask is required parameter when network is set to 'static'.") + 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): + 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 """ - pgs_list = self.get_all_port_groups_by_host(host_system=host_system) - desired_pg = None - for pg in pgs_list: - if pg.spec.name == portgroup_name: - return pg - return desired_pg + 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): """ @@ -246,13 +380,11 @@ class PyVmomiHelper(PyVmomi): """ host_vmk_states = { 'absent': { - 'update': self.host_vmk_delete, 'present': self.host_vmk_delete, 'absent': self.host_vmk_unchange, }, 'present': { - 'update': self.host_vmk_update, - 'present': self.host_vmk_unchange, + 'present': self.host_vmk_update, 'absent': self.host_vmk_create, } } @@ -263,10 +395,8 @@ class PyVmomiHelper(PyVmomi): 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)) - except Exception as e: - self.module.fail_json(msg=to_native(e)) - def get_vmkernel(self, port_group_name=None): + def get_vmkernel_by_portgroup_new(self, port_group_name=None): """ Check if vmkernel available or not Args: @@ -275,64 +405,55 @@ class PyVmomiHelper(PyVmomi): Returns: vmkernel managed object if vmkernel found, false if not """ - ret = False - vnics = [vnic for vnic in self.esxi_host_obj.config.network.vnic if vnic.spec.portgroup == port_group_name] + 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: - ret = vnics[0] - return ret + return vnics[0] + return None def check_state(self): """ Check internal state management - Returns: Present if found, absent if not, update if change in fields + Returns: Present if found and absent if not found """ state = 'absent' - self.vnic = self.get_vmkernel(port_group_name=self.port_group_name) if self.vnic: state = 'present' - if self.vnic.spec.mtu != self.mtu: - state = 'update' - if self.vnic.spec.ip.dhcp: - if self.network_type == 'static': - state = 'update' - - if not self.vnic.spec.ip.dhcp: - if self.network_type == 'dhcp': - state = 'update' - elif self.network_type == 'static': - if (self.ip_address != self.vnic.spec.ip.ipAddress) or \ - (self.subnet_mask != self.vnic.spec.ip.subnetMask): - state = 'update' - # Add for vsan, vmotion and management - 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']): - state = 'update' - - 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']): - state = 'update' - - 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']): - state = 'update' - - 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']): - state = 'update' - - 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']): - state = 'update' - - 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']): - state = 'update' - - 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']): - state = 'update' return state @@ -342,21 +463,26 @@ class PyVmomiHelper(PyVmomi): Returns: NA """ - results = dict(changed=False, result='') + results = dict(changed=False, msg='') vmk_device = self.vnic.device try: - self.esxi_host_obj.configManager.networkSystem.RemoveVirtualNic(vmk_device) - results['result'] = vmk_device + 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)) + 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)) - except Exception as e: - self.module.fail_json(msg="Failed to delete vmk due to general " - "exception : %s" % to_native(e)) + 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) @@ -374,90 +500,309 @@ class PyVmomiHelper(PyVmomi): Returns: NA """ - results = dict(changed=False, result='') - vnic_config = vim.host.VirtualNic.Specification() - ip_spec = vim.host.IpConfig() - if self.network_type == 'dhcp': - ip_spec.dhcp = True + 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: - ip_spec.dhcp = False - ip_spec.ipAddress = self.ip_address - ip_spec.subnetMask = self.subnet_mask + 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 - vnic_config.ip = ip_spec - vnic_config.mtu = self.mtu + 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 - try: - self.esxi_host_obj.configManager.networkSystem.UpdateVirtualNic(self.vnic.device, vnic_config) - results['changed'] = True - results['result'] = self.vnic.device - 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)) - except Exception as e: - self.module.fail_json(msg="Failed to add vmk due to general " - "exception : %s" % to_native(e)) - - # Query All service type for VMKernel - service_type_vmk = self.get_all_vmks_by_service_type() - - vnic_manager = self.esxi_host_obj.configManager.virtualNicManager - - if self.enable_vmotion and self.vnic.device not in service_type_vmk['vmotion']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vmotion') - elif not self.enable_vmotion and self.vnic.device in service_type_vmk['vmotion']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vmotion', operation='deselect') - - if self.enable_mgmt and self.vnic.device not in service_type_vmk['management']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='management') - elif not self.enable_mgmt and self.vnic.device in service_type_vmk['management']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='management', operation='deselect') - - if self.enable_ft and self.vnic.device not in service_type_vmk['faultToleranceLogging']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='faultToleranceLogging') - elif not self.enable_ft and self.vnic.device in service_type_vmk['faultToleranceLogging']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='faultToleranceLogging', operation='deselect') - - if self.enable_provisioning and self.vnic.device not in service_type_vmk['vSphereProvisioning']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereProvisioning') - elif not self.enable_provisioning and self.vnic.device in service_type_vmk['vSphereProvisioning']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereProvisioning', operation='deselect') - - if self.enable_replication and self.vnic.device not in service_type_vmk['vSphereReplication']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplication') - elif not self.enable_replication and self.vnic.device in service_type_vmk['vSphereReplication']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplication', operation='deselect') - - if self.enable_replication_nfc and self.vnic.device not in service_type_vmk['vSphereReplicationNFC']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplicationNFC') - elif not self.enable_replication_nfc and self.vnic.device in service_type_vmk['vSphereReplicationNFC']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vSphereReplicationNFC', operation='deselect') - - if self.enable_vsan and self.vnic.device not in service_type_vmk['vsan']: - results['changed'], results['result'] = self.set_vsan_service_type() - elif not self.enable_vsan and self.vnic.device in service_type_vmk['vsan']: - results['changed'] = self.set_service_type(vnic_manager=vnic_manager, vmk=self.vnic, service_type='vsan', operation='deselect') - - results['result'] = self.vnic.device + 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: True and result for success, False and result for failure + Returns: result of UpdateVsan_Task """ - changed, result = (False, '') + result = None vsan_system = self.esxi_host_obj.configManager.vsanSystem vsan_port_config = vim.vsan.host.ConfigInfo.NetworkInfo.PortConfig() @@ -466,13 +811,15 @@ class PyVmomiHelper(PyVmomi): vsan_config = vim.vsan.host.ConfigInfo() vsan_config.networkInfo = vim.vsan.host.ConfigInfo.NetworkInfo() vsan_config.networkInfo.port = [vsan_port_config] - try: - vsan_task = vsan_system.UpdateVsan_Task(vsan_config) - changed, result = wait_for_task(vsan_task) - except Exception as e: - self.module.fail_json(msg="Failed to set service type to vsan for" - " %s : %s" % (self.vnic.device, to_native(e))) - return changed, result + 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): """ @@ -480,64 +827,113 @@ class PyVmomiHelper(PyVmomi): Returns: NA """ - results = dict(changed=False, result='') + 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: - vmk_device = self.esxi_host_obj.configManager.networkSystem.AddVirtualNic(self.port_group_name, vnic_config) + 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['result'] = vmk_device - self.vnic = self.get_vmkernel(port_group_name=self.port_group_name) + 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)) + 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)) + 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)) + 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)) - except Exception as e: - self.module.fail_json(msg="Failed to add vmk due to general " - "exception : %s" % to_native(e)) - - # VSAN - if self.enable_vsan: - results['changed'], results['result'] = self.set_vsan_service_type() + 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) + ) - # Other service type - host_vnic_manager = self.esxi_host_obj.configManager.virtualNicManager - if self.enable_vmotion: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'vmotion') + # 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) - if self.enable_mgmt: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'management') + # VSAN + if self.enable_vsan: + results['vsan'] = self.set_vsan_service_type() - if self.enable_ft: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'faultToleranceLogging') + # 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_provisioning: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'vSphereProvisioning') + if self.enable_mgmt: + self.set_service_type(host_vnic_manager, self.vnic, 'management') - if self.enable_replication: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'vSphereReplication') + if self.enable_ft: + self.set_service_type(host_vnic_manager, self.vnic, 'faultToleranceLogging') - if self.enable_replication_nfc: - results['changed'] = self.set_service_type(host_vnic_manager, self.vnic, 'vSphereReplicationNFC') + 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) @@ -550,24 +946,19 @@ class PyVmomiHelper(PyVmomi): service_type: Name of service type operation: Select to select service type, deselect to deselect service type - Returns: True if set, false if not - """ - ret = False try: if operation == 'select': - vnic_manager.SelectVnicForNicType(service_type, vmk.device) + if not self.module.check_mode: + vnic_manager.SelectVnicForNicType(service_type, vmk.device) elif operation == 'deselect': - vnic_manager.DeselectVnicForNicType(service_type, vmk.device) - ret = True + 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 to %s due" - " to %s" % (operation, service_type, vmk.device, to_native(invalid_arg.msg))) - except Exception as e: - self.module.fail_json(msg="Failed to %s VMK service type %s to %s due" - " generic exception : %s" % (operation, service_type, vmk.device, to_native(e))) - - return ret + 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): """ @@ -585,7 +976,7 @@ class PyVmomiHelper(PyVmomi): vSphereReplicationNFC=[], ) - for service_type in service_type_vmk.keys(): + 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 @@ -604,14 +995,15 @@ class PyVmomiHelper(PyVmomi): 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))) + 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))) - except Exception as e: - self.module.fail_json(msg="Failed to get all VMKs for service type %s due to" - "%s" % (service_type, to_native(e))) + 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 @@ -619,46 +1011,110 @@ class PyVmomiHelper(PyVmomi): 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'), + 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), - enable_vsan=dict(required=False, type='bool'), - enable_vmotion=dict(required=False, type='bool'), - enable_mgmt=dict(required=False, type='bool'), - enable_ft=dict(required=False, type='bool'), - enable_provisioning=dict(type='bool'), - enable_replication=dict(type='bool'), - enable_replication_nfc=dict(type='bool'), - vswitch_name=dict(required=False, type='str'), - vlan_id=dict(required=False, type='int'), - state=dict(type='str', - choices=['present', 'absent'], - default='present'), + 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( - required=True, type='dict', options=dict( - type=dict(type='str', choices=['static', 'dhcp']), + 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', ), ), - ) - ) - - required_if = [ - ['state', 'present', ['vswitch_name', 'vlan_id']], - ] + state=dict( + type='str', + default='present', + choices=['absent', 'present'] + ), + )) module = AnsibleModule(argument_spec=argument_spec, - required_if=required_if, - supports_check_mode=False) + 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() diff --git a/test/integration/targets/vmware_vmkernel/aliases b/test/integration/targets/vmware_vmkernel/aliases new file mode 100644 index 00000000000..845e8a6dad5 --- /dev/null +++ b/test/integration/targets/vmware_vmkernel/aliases @@ -0,0 +1,2 @@ +cloud/vcenter +unsupported diff --git a/test/integration/targets/vmware_vmkernel/tasks/main.yml b/test/integration/targets/vmware_vmkernel/tasks/main.yml new file mode 100644 index 00000000000..a015b96cf76 --- /dev/null +++ b/test/integration/targets/vmware_vmkernel/tasks/main.yml @@ -0,0 +1,144 @@ +# Test code for the vmware_vmkernel module. +# Copyright: (c) 2018, Christian Kotte +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# TODO: vcsim does not support HostVirtualNicManager-related operations + +- name: store the vcenter container ip + set_fact: + vcsim: "{{ lookup('env', 'vcenter_host') }}" + +- debug: var=vcsim + +- name: Wait for Flask controller to come up online + wait_for: + host: "{{ vcsim }}" + port: 5000 + state: started + +- name: kill vcsim + uri: + url: http://{{ vcsim }}:5000/killall + +- name: start vcsim + uri: + url: http://{{ vcsim }}:5000/spawn?cluster=2 + register: vcsim_instance + +- debug: + var: vcsim_instance + +- name: Wait for vcsim server to come up online + wait_for: + host: "{{ vcsim }}" + port: 443 + state: started + +- name: get a list of hosts from vcsim + uri: + url: http://{{ vcsim }}:5000/govc_find?filter=H + register: hosts + +- name: get a host + set_fact: + host1: "{{ hosts.json[0] | basename }}" + +- debug: var=host1 + +- name: Create VMkernel adapter + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + mtu: 1500 + enable_vmotion: True + state: present + validate_certs: no + register: host_vmkernel + +- debug: var=host_vmkernel + +- name: Create VMkernel adapter in check mode + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + mtu: 1500 + enable_vmotion: True + state: present + validate_certs: no + register: host_vmkernel + check_mode: yes + +- debug: var=host_vmkernel + +- name: Delete VMkernel adapter + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + state: absent + validate_certs: no + register: host_vmkernel + +- debug: var=host_vmkernel + +- name: Create VMkernel adapter with vMotion TCP/IP stack + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + mtu: 9000 + network: + type: static + ip_address: 192.168.100.100 + subnet_mask: 255.255.255.0 + tcpip_stack: vmotion + state: present + validate_certs: no + register: host_vmkernel + +- debug: var=host_vmkernel + +- name: Delete VMkernel adapter + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + state: absent + validate_certs: no + register: host_vmkernel + +- debug: var=host_vmkernel + +- name: Create VMkernel adapter with DHCP + vmware_vmkernel: + hostname: "{{ vcsim }}" + username: "{{ user }}" + password: "{{ passwd }}" + esxi_hostname: "{{ host1 }}" + vswitch: vSwitch1 + portgroup: vMotion + mtu: 9000 + network: + type: dhcp + state: present + validate_certs: no + register: host_vmkernel + +- debug: var=host_vmkernel