VMware: add new module vmware_guest_network (#52075)

pull/58627/head
Diane Wang 5 years ago committed by Abhijeet Kasurde
parent 5b953581b4
commit 9c140d26d5

@ -3,6 +3,7 @@
# General networking tools that may be used by all modules # General networking tools that may be used by all modules
import re
from struct import pack from struct import pack
from socket import inet_ntoa from socket import inet_ntoa
@ -143,3 +144,15 @@ def to_bits(val):
for octet in val.split('.'): for octet in val.split('.'):
bits += bin(int(octet))[2:].zfill(8) bits += bin(int(octet))[2:].zfill(8)
return str return str
def is_mac(mac_address):
"""
Validate MAC address for given string
Args:
mac_address: string to validate as MAC address
Returns: (Boolean) True if string is valid MAC address, otherwise False
"""
mac_addr_regex = re.compile('[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$')
return bool(mac_addr_regex.match(mac_address.lower()))

@ -832,7 +832,7 @@ class PyVmomi(object):
def get_managed_objects_properties(self, vim_type, properties=None): def get_managed_objects_properties(self, vim_type, properties=None):
""" """
Function to look up a Managed Object Reference in vCenter / ESXi Environment Look up a Managed Object Reference in vCenter / ESXi Environment
:param vim_type: Type of vim object e.g, for datacenter - vim.Datacenter :param vim_type: Type of vim object e.g, for datacenter - vim.Datacenter
:param properties: List of properties related to vim object e.g. Name :param properties: List of properties related to vim object e.g. Name
:return: local content object :return: local content object
@ -880,7 +880,7 @@ class PyVmomi(object):
# Virtual Machine related functions # Virtual Machine related functions
def get_vm(self): def get_vm(self):
""" """
Function to find unique virtual machine either by UUID or Name. Find unique virtual machine either by UUID or Name.
Returns: virtual machine object if found, else None. Returns: virtual machine object if found, else None.
""" """
@ -967,7 +967,7 @@ class PyVmomi(object):
def gather_facts(self, vm): def gather_facts(self, vm):
""" """
Function to gather facts of virtual machine. Gather facts of virtual machine.
Args: Args:
vm: Name of virtual machine. vm: Name of virtual machine.
@ -979,7 +979,7 @@ class PyVmomi(object):
@staticmethod @staticmethod
def get_vm_path(content, vm_name): def get_vm_path(content, vm_name):
""" """
Function to find the path of virtual machine. Find the path of virtual machine.
Args: Args:
content: VMware content object content: VMware content object
vm_name: virtual machine managed object vm_name: virtual machine managed object
@ -1088,7 +1088,7 @@ class PyVmomi(object):
def get_all_host_objs(self, cluster_name=None, esxi_host_name=None): def get_all_host_objs(self, cluster_name=None, esxi_host_name=None):
""" """
Function to get all host system managed object Get all host system managed object
Args: Args:
cluster_name: Name of Cluster cluster_name: Name of Cluster
@ -1163,7 +1163,7 @@ class PyVmomi(object):
def get_all_port_groups_by_host(self, host_system): def get_all_port_groups_by_host(self, host_system):
""" """
Function to get all Port Group by host Get all Port Group by host
Args: Args:
host_system: Name of Host System host_system: Name of Host System
@ -1174,10 +1174,48 @@ class PyVmomi(object):
pgs_list.append(pg) pgs_list.append(pg)
return pgs_list return pgs_list
def find_network_by_name(self, network_name=None):
"""
Get network specified by name
Args:
network_name: Name of network
Returns: List of network managed objects
"""
networks = []
if not network_name:
return networks
objects = self.get_managed_objects_properties(vim_type=vim.Network, properties=['name'])
for temp_vm_object in objects:
if len(temp_vm_object.propSet) != 1:
continue
for temp_vm_object_property in temp_vm_object.propSet:
if temp_vm_object_property.val == self.params['name']:
networks.append(temp_vm_object.obj)
break
return networks
def network_exists_by_name(self, network_name=None):
"""
Check if network with a specified name exists or not
Args:
network_name: Name of network
Returns: True if network exists else False
"""
ret = False
if not network_name:
return ret
ret = True if self.find_network_by_name(network_name=network_name) else False
return ret
# Datacenter # Datacenter
def find_datacenter_by_name(self, datacenter_name): def find_datacenter_by_name(self, datacenter_name):
""" """
Function to get datacenter managed object by name Get datacenter managed object by name
Args: Args:
datacenter_name: Name of datacenter datacenter_name: Name of datacenter
@ -1189,7 +1227,7 @@ class PyVmomi(object):
def find_datastore_by_name(self, datastore_name): def find_datastore_by_name(self, datastore_name):
""" """
Function to get datastore managed object by name Get datastore managed object by name
Args: Args:
datastore_name: Name of datastore datastore_name: Name of datastore
@ -1201,7 +1239,7 @@ class PyVmomi(object):
# Datastore cluster # Datastore cluster
def find_datastore_cluster_by_name(self, datastore_cluster_name): def find_datastore_cluster_by_name(self, datastore_cluster_name):
""" """
Function to get datastore cluster managed object by name Get datastore cluster managed object by name
Args: Args:
datastore_cluster_name: Name of datastore cluster datastore_cluster_name: Name of datastore cluster

@ -18,6 +18,7 @@ except ImportError:
pass pass
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
from ansible.module_utils.common.network import is_mac
from ansible.module_utils.ansible_release import __version__ as ANSIBLE_VERSION from ansible.module_utils.ansible_release import __version__ as ANSIBLE_VERSION
@ -72,19 +73,6 @@ def module_to_xapi_vm_power_state(power_state):
return vm_power_state_map.get(power_state) return vm_power_state_map.get(power_state)
def is_valid_mac_addr(mac_addr):
"""Validates given string as MAC address.
Args:
mac_addr (str): string to validate as MAC address.
Returns:
bool: True if string is valid MAC address, else False.
"""
mac_addr_regex = re.compile('[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$')
return bool(mac_addr_regex.match(mac_addr.lower()))
def is_valid_ip_addr(ip_addr): def is_valid_ip_addr(ip_addr):
"""Validates given string as IPv4 address for given string. """Validates given string as IPv4 address for given string.

@ -103,7 +103,7 @@ state:
''' '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
import re from ansible.module_utils.common.network import is_mac
class NicTag(object): class NicTag(object):
@ -121,9 +121,7 @@ class NicTag(object):
self.nictagadm_bin = self.module.get_bin_path('nictagadm', True) self.nictagadm_bin = self.module.get_bin_path('nictagadm', True)
def is_valid_mac(self): def is_valid_mac(self):
if re.match("[0-9a-f]{2}([:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", self.mac.lower()): return is_mac(self.mac.lower())
return True
return False
def nictag_exists(self): def nictag_exists(self):
cmd = [self.nictagadm_bin] cmd = [self.nictagadm_bin]

@ -589,6 +589,7 @@ except ImportError:
from random import randint from random import randint
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.network import is_mac
from ansible.module_utils._text import to_text, to_native from ansible.module_utils._text import to_text, to_native
from ansible.module_utils.vmware import (find_obj, gather_vm_facts, get_all_objs, from ansible.module_utils.vmware import (find_obj, gather_vm_facts, get_all_objs,
compile_folder_path_for_object, serialize_spec, compile_folder_path_for_object, serialize_spec,
@ -729,7 +730,7 @@ class PyVmomiDeviceHelper(object):
nic.device.connectable.startConnected = bool(device_infos.get('start_connected', True)) nic.device.connectable.startConnected = bool(device_infos.get('start_connected', True))
nic.device.connectable.allowGuestControl = bool(device_infos.get('allow_guest_control', True)) nic.device.connectable.allowGuestControl = bool(device_infos.get('allow_guest_control', True))
nic.device.connectable.connected = True nic.device.connectable.connected = True
if 'mac' in device_infos and self.is_valid_mac_addr(device_infos['mac']): if 'mac' in device_infos and is_mac(device_infos['mac']):
nic.device.addressType = 'manual' nic.device.addressType = 'manual'
nic.device.macAddress = device_infos['mac'] nic.device.macAddress = device_infos['mac']
else: else:
@ -737,18 +738,6 @@ class PyVmomiDeviceHelper(object):
return nic return nic
@staticmethod
def is_valid_mac_addr(mac_addr):
"""
Function to validate MAC address for given string
Args:
mac_addr: string to validate as MAC address
Returns: (Boolean) True if string is valid MAC address, otherwise False
"""
mac_addr_regex = re.compile('[0-9a-f]{2}([-:])[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$')
return bool(mac_addr_regex.match(mac_addr))
def integer_value(self, input_value, name): def integer_value(self, input_value, name):
""" """
Function to return int value for given input, else return error Function to return int value for given input, else return error
@ -1273,7 +1262,7 @@ class PyVmomiHelper(PyVmomi):
" type from ['%s']." % (network['device_type'], " type from ['%s']." % (network['device_type'],
"', '".join(validate_device_types))) "', '".join(validate_device_types)))
if 'mac' in network and not PyVmomiDeviceHelper.is_valid_mac_addr(network['mac']): if 'mac' in network and not is_mac(network['mac']):
self.module.fail_json(msg="Device MAC address '%s' is invalid." self.module.fail_json(msg="Device MAC address '%s' is invalid."
" Please provide correct MAC address." % network['mac']) " Please provide correct MAC address." % network['mac'])

@ -0,0 +1,461 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Ansible Project
# Copyright: (c) 2019, Diane Wang <dianew@vmware.com>
# 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_guest_network
short_description: Manage network adapters of specified virtual machine in given vCenter infrastructure
description:
- This module is used to add, reconfigure, remove network adapter of given virtual machine.
- All parameters and VMware object names are case sensitive.
version_added: '2.9'
author:
- Diane Wang (@Tomorrow9) <dianew@vmware.com>
notes:
- Tested on vSphere 6.0, 6.5 and 6.7
requirements:
- "python >= 2.6"
- PyVmomi
options:
name:
description:
- Name of the virtual machine.
- This is a required parameter, if parameter C(uuid) is not supplied.
type: str
uuid:
description:
- UUID of the instance to gather facts if known, this is VMware's unique identifier.
- This is a required parameter, if parameter C(name) is not supplied.
type: str
folder:
description:
- Destination folder, absolute or relative path to find an existing guest.
- This is a required parameter, only if multiple VMs are found with same name.
- The folder should include the datacenter. ESXi server's datacenter is ha-datacenter.
- 'Examples:'
- ' folder: /ha-datacenter/vm'
- ' folder: ha-datacenter/vm'
- ' folder: /datacenter1/vm'
- ' folder: datacenter1/vm'
- ' folder: /datacenter1/vm/folder1'
- ' folder: datacenter1/vm/folder1'
- ' folder: /folder1/datacenter1/vm'
- ' folder: folder1/datacenter1/vm'
- ' folder: /folder1/datacenter1/vm/folder2'
type: str
cluster:
description:
- The name of cluster where the virtual machine will run.
- This is a required parameter, if C(esxi_hostname) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
type: str
esxi_hostname:
description:
- The ESXi hostname where the virtual machine will run.
- This is a required parameter, if C(cluster) is not set.
- C(esxi_hostname) and C(cluster) are mutually exclusive parameters.
type: str
datacenter:
default: ha-datacenter
description:
- The datacenter name to which virtual machine belongs to.
type: str
gather_network_facts:
description:
- If set to C(True), return settings of all network adapters, other parameters are ignored.
- If set to C(False), will add, reconfigure or remove network adapters according to the parameters in C(networks).
type: bool
default: False
networks:
type: list
description:
- A list of network adapters.
- C(mac) or C(label) or C(device_type) is required to reconfigure or remove an existing network adapter.
- 'If there are multiple network adapters with the same C(device_type), you should set C(label) or C(mac) to match
one of them, or will apply changes on all network adapters with the C(device_type) specified.'
- 'C(mac), C(label), C(device_type) is the order of precedence from greatest to least if all set.'
- 'Valid attributes are:'
- ' - C(mac) (string): MAC address of the existing network adapter to be reconfigured or removed.'
- ' - C(label) (string): Label of the existing network adapter to be reconfigured or removed, e.g., "Network adapter 1".'
- ' - C(device_type) (string): Valid virtual network device types are:
C(e1000), C(e1000e), C(pcnet32), C(vmxnet2), C(vmxnet3) (default), C(sriov).
Used to add new network adapter, reconfigure or remove the existing network adapter with this type.
If C(mac) and C(label) not specified or not find network adapter by C(mac) or C(label) will use this parameter.'
- ' - C(name) (string): Name of the portgroup or distributed virtual portgroup for this interface.
When specifying distributed virtual portgroup make sure given C(esxi_hostname) or C(cluster) is associated with it.'
- ' - C(vlan) (integer): VLAN number for this interface.'
- ' - C(dvswitch_name) (string): Name of the distributed vSwitch.
This value is required if multiple distributed portgroups exists with the same name.'
- ' - C(state) (string): State of the network adapter.'
- ' If set to C(present), then will do reconfiguration for the specified network adapter.'
- ' If set to C(new), then will add the specified network adapter.'
- ' If set to C(absent), then will remove this network adapter.'
- ' - C(manual_mac) (string): Manual specified MAC address of the network adapter when creating, or reconfiguring.
If not specified when creating new network adapter, mac address will be generated automatically.
When reconfigure MAC address, VM should be in powered off state.'
- ' - C(connected) (bool): Indicates that virtual network adapter connects to the associated virtual machine.'
- ' - C(start_connected) (bool): Indicates that virtual network adapter starts with associated virtual machine powers on.'
extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
- name: Change network adapter settings of virtual machine
vmware_guest_network:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
datacenter: "{{ datacenter_name }}"
validate_certs: no
name: test-vm
gather_network_facts: false
networks:
- name: "VM Network"
state: new
manual_mac: "00:50:56:11:22:33"
- state: present
device_type: e1000e
manual_mac: "00:50:56:44:55:66"
- state: present
label: "Network adapter 3"
connected: false
- state: absent
mac: "00:50:56:44:55:77"
delegate_to: localhost
register: network_facts
'''
RETURN = """
network_data:
description: metadata about the virtual machine's network adapter after managing them
returned: always
type: dict
sample: {
"0": {
"label": "Network Adapter 1",
"name": "VM Network",
"device_type": "E1000E",
"mac_addr": "00:50:56:89:dc:05",
"unit_number": 7,
"wake_onlan": false,
"allow_guest_ctl": true,
"connected": true,
"start_connected": true,
},
}
"""
import re
try:
from pyVmomi import vim
except ImportError:
pass
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.network import is_mac
from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, wait_for_task, find_obj, get_all_objs, get_parent_datacenter
class PyVmomiHelper(PyVmomi):
def __init__(self, module):
super(PyVmomiHelper, self).__init__(module)
self.change_detected = False
self.config_spec = vim.vm.ConfigSpec()
self.config_spec.deviceChange = []
self.nic_device_type = dict(
pcnet32=vim.vm.device.VirtualPCNet32,
vmxnet2=vim.vm.device.VirtualVmxnet2,
vmxnet3=vim.vm.device.VirtualVmxnet3,
e1000=vim.vm.device.VirtualE1000,
e1000e=vim.vm.device.VirtualE1000e,
sriov=vim.vm.device.VirtualSriovEthernetCard,
)
def get_device_type(self, device_type=None):
""" Get network adapter device type """
if device_type and device_type in list(self.nic_device_type.keys()):
return self.nic_device_type[device_type]()
else:
self.module.fail_json(msg='Invalid network device_type %s' % device_type)
def get_network_device(self, vm=None, mac=None, device_type=None, device_label=None):
"""
Get network adapter
"""
nic_devices = []
nic_device = None
if vm is None:
if device_type:
return nic_devices
else:
return nic_device
for device in vm.config.hardware.device:
if mac:
if isinstance(device, vim.vm.device.VirtualEthernetCard):
if device.macAddress == mac:
nic_device = device
break
elif device_type:
if isinstance(device, self.nic_device_type[device_type]):
nic_devices.append(device)
elif device_label:
if isinstance(device, vim.vm.device.VirtualEthernetCard):
if device.deviceInfo.label == device_label:
nic_device = device
break
if device_type:
return nic_devices
else:
return nic_device
def get_network_device_by_mac(self, vm=None, mac=None):
""" Get network adapter with the specified mac address"""
return self.get_network_device(vm=vm, mac=mac)
def get_network_devices_by_type(self, vm=None, device_type=None):
""" Get network adapter list with the name type """
return self.get_network_device(vm=vm, device_type=device_type)
def get_network_device_by_label(self, vm=None, device_label=None):
""" Get network adapter with the specified label """
return self.get_network_device(vm=vm, device_label=device_label)
def create_network_adapter(self, device_info):
nic = vim.vm.device.VirtualDeviceSpec()
nic.device = self.get_device_type(device_type=device_info.get('device_type', 'vmxnet3'))
nic.device.deviceInfo = vim.Description()
nic.device.deviceInfo.summary = device_info['name']
nic.device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
nic.device.backing.deviceName = device_info['name']
nic.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
nic.device.connectable.startConnected = device_info.get('start_connected', True)
nic.device.connectable.allowGuestControl = True
nic.device.connectable.connected = device_info.get('connected', True)
if 'manual_mac' in device_info:
nic.device.addressType = 'manual'
nic.device.macAddress = device_info['manual_mac']
else:
nic.device.addressType = 'generated'
return nic
def get_network_facts(self, vm_obj):
network_facts = dict()
if vm_obj is None:
return network_facts
nic_index = 0
for nic in vm_obj.config.hardware.device:
nic_type = None
if isinstance(nic, vim.vm.device.VirtualPCNet32):
nic_type = 'PCNet32'
elif isinstance(nic, vim.vm.device.VirtualVmxnet2):
nic_type = 'VMXNET2'
elif isinstance(nic, vim.vm.device.VirtualVmxnet3):
nic_type = 'VMXNET3'
elif isinstance(nic, vim.vm.device.VirtualE1000):
nic_type = 'E1000'
elif isinstance(nic, vim.vm.device.VirtualE1000e):
nic_type = 'E1000E'
elif isinstance(nic, vim.vm.device.VirtualSriovEthernetCard):
nic_type = 'SriovEthernetCard'
if nic_type is not None:
network_facts[nic_index] = dict(
device_type=nic_type,
label=nic.deviceInfo.label,
name=nic.deviceInfo.summary,
mac_addr=nic.macAddress,
unit_number=nic.unitNumber,
wake_onlan=nic.wakeOnLanEnabled,
allow_guest_ctl=nic.connectable.allowGuestControl,
connected=nic.connectable.connected,
start_connected=nic.connectable.startConnected,
)
nic_index += 1
return network_facts
def sanitize_network_params(self):
network_list = []
valid_state = ['new', 'present', 'absent']
if len(self.params['networks']) != 0:
for network in self.params['networks']:
if 'state' not in network or network['state'].lower() not in valid_state:
self.module.fail_json(msg="Network adapter state not specified or invalid: '%s', valid values: "
"%s" % (network.get('state', ''), valid_state))
# add new network adapter but no name specified
if network['state'].lower() == 'new' and 'name' not in network and 'vlan' not in network:
self.module.fail_json(msg="Please specify at least network name or VLAN name for adding new network adapter.")
if network['state'].lower() == 'new' and 'mac' in network:
self.module.fail_json(msg="networks.mac is used for vNIC reconfigure, but networks.state is set to 'new'.")
if network['state'].lower() == 'present' and 'mac' not in network and 'label' not in network and 'device_type' not in network:
self.module.fail_json(msg="Should specify 'mac', 'label' or 'device_type' parameter to reconfigure network adapter")
if 'connected' in network:
if not isinstance(network['connected'], bool):
self.module.fail_json(msg="networks.connected parameter should be boolean.")
if network['state'].lower() == 'new' and not network['connected']:
network['start_connected'] = False
if 'start_connected' in network:
if not isinstance(network['start_connected'], bool):
self.module.fail_json(msg="networks.start_connected parameter should be boolean.")
if network['state'].lower() == 'new' and not network['start_connected']:
network['connected'] = False
# specified network not exist
if 'name' in network and not self.network_exists_by_name(self.content, network['name']):
self.module.fail_json(msg="Network '%(name)s' does not exist." % network)
elif 'vlan' in network:
objects = get_all_objs(self.content, [vim.dvs.DistributedVirtualPortgroup])
dvps = [x for x in objects if to_text(get_parent_datacenter(x).name) == to_text(self.params['datacenter'])]
for dvp in dvps:
if hasattr(dvp.config.defaultPortConfig, 'vlan') and \
isinstance(dvp.config.defaultPortConfig.vlan.vlanId, int) and \
str(dvp.config.defaultPortConfig.vlan.vlanId) == str(network['vlan']):
network['name'] = dvp.config.name
break
if 'dvswitch_name' in network and \
dvp.config.distributedVirtualSwitch.name == network['dvswitch_name'] and \
dvp.config.name == network['vlan']:
network['name'] = dvp.config.name
break
if dvp.config.name == network['vlan']:
network['name'] = dvp.config.name
break
else:
self.module.fail_json(msg="VLAN '%(vlan)s' does not exist." % network)
if 'device_type' in network and network['device_type'] not in list(self.nic_device_type.keys()):
self.module.fail_json(msg="Device type specified '%s' is invalid. "
"Valid types %s " % (network['device_type'], list(self.nic_device_type.keys())))
if ('mac' in network and not is_mac(network['mac'])) or \
('manual_mac' in network and not is_mac(network['manual_mac'])):
self.module.fail_json(msg="Device MAC address '%s' or manual set MAC address %s is invalid. "
"Please provide correct MAC address." % (network['mac'], network['manual_mac']))
network_list.append(network)
return network_list
def get_network_config_spec(self, vm_obj, network_list):
# create network adapter config spec for adding, editing, removing
for network in network_list:
# add new network adapter
if network['state'].lower() == 'new':
nic_spec = self.create_network_adapter(network)
nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
self.change_detected = True
self.config_spec.deviceChange.append(nic_spec)
# reconfigure network adapter or remove network adapter
else:
nic_devices = []
if 'mac' in network:
nic = self.get_network_device_by_mac(vm_obj, mac=network['mac'])
if nic is not None:
nic_devices.append(nic)
if 'label' in network and len(nic_devices) == 0:
nic = self.get_network_device_by_label(vm_obj, device_label=network['label'])
if nic is not None:
nic_devices.append(nic)
if 'device_type' in network and len(nic_devices) == 0:
nic_devices = self.get_network_devices_by_type(vm_obj, device_type=network['device_type'])
if len(nic_devices) != 0:
for nic_device in nic_devices:
nic_spec = vim.vm.device.VirtualDeviceSpec()
if network['state'].lower() == 'present':
nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
nic_spec.device = nic_device
if 'start_connected' in network and nic_device.connectable.startConnected != network['start_connected']:
nic_device.connectable.startConnected = network['start_connected']
self.change_detected = True
if 'connected' in network and nic_device.connectable.connected != network['connected']:
nic_device.connectable.connected = network['connected']
self.change_detected = True
if 'name' in network and nic_device.deviceInfo.summary != network['name']:
nic_device.deviceInfo.summary = network['name']
self.change_detected = True
if 'manual_mac' in network and nic_device.macAddress != network['manual_mac']:
if vm_obj.runtime.powerState != vim.VirtualMachinePowerState.poweredOff:
self.module.fail_json(msg='Expected power state is poweredOff to reconfigure MAC address')
nic_device.addressType = 'manual'
nic_device.macAddress = network['manual_mac']
self.change_detected = True
if self.change_detected:
self.config_spec.deviceChange.append(nic_spec)
elif network['state'].lower() == 'absent':
nic_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
nic_spec.device = nic_device
self.change_detected = True
self.config_spec.deviceChange.append(nic_spec)
else:
self.module.fail_json(msg='Unable to find the specified network adapter: %s' % network)
def reconfigure_vm_network(self, vm_obj):
network_list = self.sanitize_network_params()
# gather network adapter facts only
if (self.params['gather_network_facts'] is not None and self.params['gather_network_facts']) or len(network_list) == 0:
results = {'changed': False, 'failed': False, 'network_data': self.get_network_facts(vm_obj)}
# do reconfigure then gather facts
else:
self.get_network_config_spec(vm_obj, network_list)
try:
task = vm_obj.ReconfigVM_Task(spec=self.config_spec)
wait_for_task(task)
except vim.fault.InvalidDeviceSpec as e:
self.module.fail_json(msg="Failed to configure network adapter on given virtual machine due to invalid"
" device spec : %s" % to_native(e.msg),
details="Please check ESXi server logs for more details.")
except vim.fault.RestrictedVersion as e:
self.module.fail_json(msg="Failed to reconfigure virtual machine due to"
" product versioning restrictions: %s" % to_native(e.msg))
if task.info.state == 'error':
results = {'changed': self.change_detected, 'failed': True, 'msg': task.info.error.msg}
else:
network_facts = self.get_network_facts(vm_obj)
results = {'changed': self.change_detected, 'failed': False, 'network_data': network_facts}
return results
def main():
argument_spec = vmware_argument_spec()
argument_spec.update(
name=dict(type='str'),
uuid=dict(type='str'),
folder=dict(type='str'),
datacenter=dict(type='str', default='ha-datacenter'),
esxi_hostname=dict(type='str'),
cluster=dict(type='str'),
gather_network_facts=dict(type='bool', default=False),
networks=dict(type='list', default=[])
)
module = AnsibleModule(argument_spec=argument_spec, required_one_of=[['name', 'uuid']])
pyv = PyVmomiHelper(module)
vm = pyv.get_vm()
if not vm:
module.fail_json(msg='Unable to find the specified virtual machine uuid: %s, name: %s '
% ((module.params.get('uuid')), (module.params.get('name'))))
result = pyv.reconfigure_vm_network(vm)
if result['failed']:
module.fail_json(**result)
else:
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -438,10 +438,11 @@ except ImportError:
pass pass
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.network import is_mac
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.xenserver import (xenserver_common_argument_spec, XAPI, XenServerObject, get_object_ref, from ansible.module_utils.xenserver import (xenserver_common_argument_spec, XAPI, XenServerObject, get_object_ref,
gather_vm_params, gather_vm_facts, set_vm_power_state, wait_for_vm_ip_address, gather_vm_params, gather_vm_facts, set_vm_power_state, wait_for_vm_ip_address,
is_valid_mac_addr, is_valid_ip_addr, is_valid_ip_netmask, is_valid_ip_prefix, is_valid_ip_addr, is_valid_ip_netmask, is_valid_ip_prefix,
ip_prefix_to_netmask, ip_netmask_to_prefix, ip_prefix_to_netmask, ip_netmask_to_prefix,
is_valid_ip6_addr, is_valid_ip6_prefix) is_valid_ip6_addr, is_valid_ip6_prefix)
@ -1406,7 +1407,7 @@ class XenServerVM(XenServerObject):
if network_mac is not None: if network_mac is not None:
network_mac = network_mac.lower() network_mac = network_mac.lower()
if not is_valid_mac_addr(network_mac): if not is_mac(network_mac):
self.module.fail_json(msg="VM check networks[%s]: specified MAC address '%s' is not valid!" % (position, network_mac)) self.module.fail_json(msg="VM check networks[%s]: specified MAC address '%s' is not valid!" % (position, network_mac))
# IPv4 reconfiguration. # IPv4 reconfiguration.

@ -0,0 +1,3 @@
cloud/vcenter
shippable/vcenter/group1
needs/target/prepare_vmware_tests

@ -0,0 +1,87 @@
# Test code for the vmware_guest_network module
# Copyright: (c) 2019, Diane Wang (Tomorrow9) <dianew@vmware.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- when: vcsim is not defined
block:
- import_role:
name: prepare_vmware_tests
vars:
setup_attach_host: true
setup_datastore: true
setup_virtualmachines: true
- name: gather network adapters' facts of the virtual machine
vmware_guest_network:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
gather_network_facts: true
register: netadapter_facts
- debug: var=netadapter_facts
- name: get number of existing netowrk adapters
set_fact:
netadapter_num: "{{ netadapter_facts.network_data | length }}"
- name: add new network adapters to virtual machine
vmware_guest_network:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
networks:
- name: "VM Network"
state: new
device_type: e1000e
manual_mac: "00:50:56:58:59:60"
- name: "VM Network"
state: new
device_type: vmxnet3
manual_mac: "00:50:56:58:59:61"
register: add_netadapter
- debug: var=add_netadapter
- name: assert the new netowrk adapters were added to VM
assert:
that:
- "add_netadapter.changed == true"
- "{{ add_netadapter.network_data | length | int }} == {{ netadapter_num | int + 2 }}"
- name: delete one specified network adapter
vmware_guest_network:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
networks:
- state: absent
mac: "00:50:56:58:59:60"
register: del_netadapter
- debug: var=del_netadapter
- name: assert the network adapter was removed
assert:
that:
- "del_netadapter.changed == true"
- "{{ del_netadapter.network_data | length | int }} == {{ netadapter_num | int + 1 }}"
- name: disconnect one specified network adapter
vmware_guest_network:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ infra.vm_list[0] }}"
networks:
- state: present
mac: "00:50:56:58:59:61"
connected: false
register: disc_netadapter
- debug: var=disc_netadapter
- name: assert the network adapter was disconnected
assert:
that:
- "disc_netadapter.changed == true"
- "{{ disc_netadapter.network_data[netadapter_num]['connected'] }} == false"

@ -10,7 +10,7 @@ __metaclass__ = type
import pytest import pytest
from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException
from ansible.module_utils.common.network import is_mac
testcase_is_valid_mac_addr = [ testcase_is_valid_mac_addr = [
('A4-23-8D-F8-C9-E5', True), ('A4-23-8D-F8-C9-E5', True),
@ -138,7 +138,7 @@ testcase_is_valid_ip6_prefix = [
@pytest.mark.parametrize('mac_addr, result', testcase_is_valid_mac_addr) @pytest.mark.parametrize('mac_addr, result', testcase_is_valid_mac_addr)
def test_is_valid_mac_addr(xenserver, mac_addr, result): def test_is_valid_mac_addr(xenserver, mac_addr, result):
"""Tests against examples of valid and invalid mac addresses.""" """Tests against examples of valid and invalid mac addresses."""
assert xenserver.is_valid_mac_addr(mac_addr) is result assert is_mac(mac_addr) is result
@pytest.mark.parametrize('ip_addr, result', testcase_is_valid_ip_addr) @pytest.mark.parametrize('ip_addr, result', testcase_is_valid_ip_addr)

Loading…
Cancel
Save