VMware: Refactor vmware_vswitch (#36091)

* Update documentation
* Update logic
* Added idempotency
* Added Error handling

Fixes: #36030

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
pull/39495/head
Abhijeet Kasurde 7 years ago committed by GitHub
parent 57a009d4c7
commit ed141f1eab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1100,7 +1100,7 @@ class PyVmomi(object):
""" """
Function to get datastore cluster managed object by name Function to get datastore cluster managed object by name
Args: Args:
datastore_cluster: Name of datastore cluster datastore_cluster_name: Name of datastore cluster
Returns: Datastore cluster managed object if found else None Returns: Datastore cluster managed object if found else None

@ -1,28 +1,33 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright: (c) 2015, Joseph Callen <jcallen () csc.com> # Copyright: (c) 2015, Joseph Callen <jcallen () csc.com>
# Copyright: (c) 2018, Ansible Project
# Copyright: (c) 2018, Abhijeet Kasurde <akasurde@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # 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 from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], ANSIBLE_METADATA = {
'supported_by': 'community'} 'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = ''' DOCUMENTATION = '''
--- ---
module: vmware_vswitch module: vmware_vswitch
short_description: Add or remove a VMware Standard Switch to an ESXi host short_description: Manage a VMware Standard Switch to an ESXi host.
description: description:
- Add or remove a VMware Standard Switch to an ESXi host. - This module can be used to add, remove and update a VMware Standard Switch to an ESXi host.
version_added: 2.0 version_added: 2.0
author: author:
- Joseph Callen (@jcpowermac) - Joseph Callen (@jcpowermac)
- Russell Teague (@mtnbikenc) - Russell Teague (@mtnbikenc)
- Abhijeet Kasurde (@akasurde) <akasurde@redhat.com>
notes: notes:
- Tested on vSphere 5.5 - Tested on vSphere 5.5 and 6.5
requirements: requirements:
- python >= 2.6 - python >= 2.6
- PyVmomi - PyVmomi
@ -38,6 +43,7 @@ options:
- A list of vmnic names or vmnic name to attach to vSwitch. - A list of vmnic names or vmnic name to attach to vSwitch.
- Alias C(nics) is added in version 2.4. - Alias C(nics) is added in version 2.4.
aliases: [ nic_name ] aliases: [ nic_name ]
default: []
number_of_ports: number_of_ports:
description: description:
- Number of port to configure on vSwitch. - Number of port to configure on vSwitch.
@ -53,7 +59,7 @@ options:
choices: [ absent, present ] choices: [ absent, present ]
esxi_hostname: esxi_hostname:
description: description:
- Manage the vSwitch using this ESXi host system - Manage the vSwitch using this ESXi host system.
version_added: "2.5" version_added: "2.5"
aliases: [ 'host' ] aliases: [ 'host' ]
extends_documentation_fragment: extends_documentation_fragment:
@ -105,21 +111,22 @@ EXAMPLES = '''
delegate_to: localhost delegate_to: localhost
''' '''
RETURN = """
result:
description: information about performed operation
returned: always
type: string
sample: "vSwitch 'vSwitch_1002' is created successfully"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, get_all_objs
try: try:
from pyVmomi import vim, vmodl from pyVmomi import vim, vmodl
except ImportError: except ImportError:
pass pass
from ansible.module_utils.basic import AnsibleModule
def find_vswitch_by_name(host, vswitch_name): from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec
for vss in host.configManager.networkSystem.networkInfo.vswitch: from ansible.module_utils._text import to_native
if vss.name == vswitch_name:
return vss
return None
class VMwareHostVirtualSwitch(PyVmomi): class VMwareHostVirtualSwitch(PyVmomi):
@ -132,96 +139,260 @@ class VMwareHostVirtualSwitch(PyVmomi):
self.nics = module.params['nics'] self.nics = module.params['nics']
self.mtu = module.params['mtu'] self.mtu = module.params['mtu']
self.state = module.params['state'] self.state = module.params['state']
self.esxi_hostname = module.params['esxi_hostname'] esxi_hostname = module.params['esxi_hostname']
hosts = self.get_all_host_objs(esxi_host_name=esxi_hostname)
if hosts:
self.host_system = hosts[0]
else:
self.module.fail_json(msg="Failed to get details of ESXi server."
" Please specify esxi_hostname.")
if self.params.get('state') == 'present':
# Gather information about all vSwitches and Physical NICs
network_manager = self.host_system.configManager.networkSystem
available_pnic = [pnic.device for pnic in network_manager.networkInfo.pnic]
self.available_vswitches = dict()
for available_vswitch in network_manager.networkInfo.vswitch:
used_pnic = []
for pnic in available_vswitch.pnic:
# vSwitch contains all PNICs as string in format of 'key-vim.host.PhysicalNic-vmnic0'
m_pnic = pnic.split("-", 3)[-1]
used_pnic.append(m_pnic)
self.available_vswitches[available_vswitch.name] = dict(pnic=used_pnic,
mtu=available_vswitch.mtu,
num_ports=available_vswitch.spec.numPorts,
)
for desired_pnic in self.nics:
if desired_pnic not in available_pnic:
# Check if pnic does not exists
self.module.fail_json(msg="Specified Physical NIC '%s' does not"
" exists on given ESXi '%s'." % (desired_pnic,
self.host_system.name))
for vswitch in self.available_vswitches:
if desired_pnic in self.available_vswitches[vswitch]['pnic'] and vswitch != self.switch:
# Check if pnic is already part of some other vSwitch
self.module.fail_json(msg="Specified Physical NIC '%s' is already used"
" by vSwitch '%s'." % (desired_pnic, vswitch))
def process_state(self): def process_state(self):
try: """
vswitch_states = { Function to manage internal state of vSwitch
'absent': { """
'present': self.state_destroy_vswitch, vswitch_states = {
'absent': self.state_exit_unchanged, 'absent': {
}, 'present': self.state_destroy_vswitch,
'present': { 'absent': self.state_exit_unchanged,
'update': self.state_update_vswitch, },
'present': self.state_exit_unchanged, 'present': {
'absent': self.state_create_vswitch, 'present': self.state_update_vswitch,
} 'absent': self.state_create_vswitch,
} }
}
try:
vswitch_states[self.state][self.check_vswitch_configuration()]() vswitch_states[self.state][self.check_vswitch_configuration()]()
except vmodl.RuntimeFault as runtime_fault: except vmodl.RuntimeFault as runtime_fault:
self.module.fail_json(msg=runtime_fault.msg) self.module.fail_json(msg=to_native(runtime_fault.msg))
except vmodl.MethodFault as method_fault: except vmodl.MethodFault as method_fault:
self.module.fail_json(msg=method_fault.msg) self.module.fail_json(msg=to_native(method_fault.msg))
except Exception as e: except Exception as e:
self.module.fail_json(msg=str(e)) self.module.fail_json(msg=to_native(e))
# Source from
# https://github.com/rreubenur/pyvmomi-community-samples/blob/patch-1/samples/create_vswitch.py
def state_create_vswitch(self): def state_create_vswitch(self):
"""
Function to create a virtual switch
Source from
https://github.com/rreubenur/pyvmomi-community-samples/blob/patch-1/samples/create_vswitch.py
"""
results = dict(changed=False, result="")
vss_spec = vim.host.VirtualSwitch.Specification() vss_spec = vim.host.VirtualSwitch.Specification()
vss_spec.numPorts = self.number_of_ports vss_spec.numPorts = self.number_of_ports
vss_spec.mtu = self.mtu vss_spec.mtu = self.mtu
if self.nics: if self.nics:
vss_spec.bridge = vim.host.VirtualSwitch.BondBridge(nicDevice=self.nics) vss_spec.bridge = vim.host.VirtualSwitch.BondBridge(nicDevice=self.nics)
self.host_system.configManager.networkSystem.AddVirtualSwitch(vswitchName=self.switch, spec=vss_spec) try:
self.module.exit_json(changed=True) network_mgr = self.host_system.configManager.networkSystem
if network_mgr:
network_mgr.AddVirtualSwitch(vswitchName=self.switch,
spec=vss_spec)
results['changed'] = True
results['result'] = "vSwitch '%s' is created successfully" % self.switch
else:
self.module.fail_json(msg="Failed to find network manager for ESXi system")
except vim.fault.AlreadyExists as already_exists:
results['result'] = "vSwitch with name %s already exists: %s" % (self.switch,
to_native(already_exists.msg))
except vim.fault.ResourceInUse as resource_used:
self.module.fail_json(msg="Failed to add vSwitch '%s' as physical network adapter"
" being bridged is already in use: %s" % (self.switch,
to_native(resource_used.msg)))
except vim.fault.HostConfigFault as host_config_fault:
self.module.fail_json(msg="Failed to add vSwitch '%s' due to host"
" configuration fault : %s" % (self.switch,
to_native(host_config_fault.msg)))
except vmodl.fault.InvalidArgument as invalid_argument:
self.module.fail_json(msg="Failed to add vSwitch '%s', this can be due to either of following :"
" 1. vSwitch Name exceeds the maximum allowed length,"
" 2. Number of ports specified falls out of valid range,"
" 3. Network policy is invalid,"
" 4. Beacon configuration is invalid : %s" % (self.switch,
to_native(invalid_argument.msg)))
except vmodl.fault.SystemError as system_error:
self.module.fail_json(msg="Failed to add vSwitch '%s' due to : %s" % (self.switch,
to_native(system_error.msg)))
except Exception as generic_exc:
self.module.fail_json(msg="Failed to add vSwitch '%s' due to"
" generic exception : %s" % (self.switch,
to_native(generic_exc)))
self.module.exit_json(**results)
def state_exit_unchanged(self): def state_exit_unchanged(self):
"""
Function to declare exit without unchanged
"""
self.module.exit_json(changed=False) self.module.exit_json(changed=False)
def state_destroy_vswitch(self): def state_destroy_vswitch(self):
config = vim.host.NetworkConfig() """
Function to remove vSwitch from configuration
for portgroup in self.host_system.configManager.networkSystem.networkInfo.portgroup:
if portgroup.spec.vswitchName == self.vss.name: """
portgroup_config = vim.host.PortGroup.Config() results = dict(changed=False, result="")
portgroup_config.changeOperation = "remove"
portgroup_config.spec = vim.host.PortGroup.Specification() try:
portgroup_config.spec.name = portgroup.spec.name self.host_system.configManager.networkSystem.RemoveVirtualSwitch(self.vss.name)
portgroup_config.spec.name = portgroup.spec.name results['changed'] = True
portgroup_config.spec.vlanId = portgroup.spec.vlanId results['result'] = "vSwitch '%s' removed successfully." % self.vss.name
portgroup_config.spec.vswitchName = portgroup.spec.vswitchName except vim.fault.NotFound as vswitch_not_found:
portgroup_config.spec.policy = vim.host.NetworkPolicy() results['result'] = "vSwitch '%s' not available. %s" % (self.switch,
config.portgroup.append(portgroup_config) to_native(vswitch_not_found.msg))
except vim.fault.ResourceInUse as vswitch_in_use:
self.host_system.configManager.networkSystem.UpdateNetworkConfig(config, "modify") self.module.fail_json(msg="Failed to remove vSwitch '%s' as vSwitch"
self.host_system.configManager.networkSystem.RemoveVirtualSwitch(self.vss.name) " is used by several virtual"
self.module.exit_json(changed=True) " network adapters: %s" % (self.switch,
to_native(vswitch_in_use.msg)))
except vim.fault.HostConfigFault as host_config_fault:
self.module.fail_json(msg="Failed to remove vSwitch '%s' due to host"
" configuration fault : %s" % (self.switch,
to_native(host_config_fault.msg)))
except Exception as generic_exc:
self.module.fail_json(msg="Failed to remove vSwitch '%s' due to generic"
" exception : %s" % (self.switch,
to_native(generic_exc)))
self.module.exit_json(**results)
def state_update_vswitch(self): def state_update_vswitch(self):
self.module.exit_json(changed=False, msg="Currently not implemented.") """
Function to update vSwitch
"""
results = dict(changed=False, result="No change in vSwitch '%s'" % self.switch)
vswitch_pnic_info = self.available_vswitches[self.switch]
remain_pnic = []
for desired_pnic in self.nics:
if desired_pnic not in vswitch_pnic_info['pnic']:
remain_pnic.append(desired_pnic)
diff = False
# Update all nics
all_nics = vswitch_pnic_info['pnic']
if remain_pnic:
all_nics += remain_pnic
diff = True
# vSwitch needs every parameter again while updating,
# even if we are updating any one of them
vss_spec = vim.host.VirtualSwitch.Specification()
vss_spec.bridge = vim.host.VirtualSwitch.BondBridge(nicDevice=all_nics)
vss_spec.numPorts = self.number_of_ports
vss_spec.mtu = self.mtu
if vswitch_pnic_info['mtu'] != self.mtu or \
vswitch_pnic_info['num_ports'] != self.number_of_ports:
diff = True
try:
if diff:
network_mgr = self.host_system.configManager.networkSystem
if network_mgr:
network_mgr.UpdateVirtualSwitch(vswitchName=self.switch,
spec=vss_spec)
results['changed'] = True
results['result'] = "vSwitch '%s' is updated successfully" % self.switch
else:
self.module.fail_json(msg="Failed to find network manager for ESXi system.")
except vim.fault.ResourceInUse as resource_used:
self.module.fail_json(msg="Failed to update vSwitch '%s' as physical network adapter"
" being bridged is already in use: %s" % (self.switch,
to_native(resource_used.msg)))
except vim.fault.NotFound as not_found:
self.module.fail_json(msg="Failed to update vSwitch with name '%s'"
" as it does not exists: %s" % (self.switch,
to_native(not_found.msg)))
except vim.fault.HostConfigFault as host_config_fault:
self.module.fail_json(msg="Failed to update vSwitch '%s' due to host"
" configuration fault : %s" % (self.switch,
to_native(host_config_fault.msg)))
except vmodl.fault.InvalidArgument as invalid_argument:
self.module.fail_json(msg="Failed to update vSwitch '%s', this can be due to either of following :"
" 1. vSwitch Name exceeds the maximum allowed length,"
" 2. Number of ports specified falls out of valid range,"
" 3. Network policy is invalid,"
" 4. Beacon configuration is invalid : %s" % (self.switch,
to_native(invalid_argument.msg)))
except vmodl.fault.SystemError as system_error:
self.module.fail_json(msg="Failed to update vSwitch '%s' due to : %s" % (self.switch,
to_native(system_error.msg)))
except vmodl.fault.NotSupported as not_supported:
self.module.fail_json(msg="Failed to update vSwitch '%s' as network adapter teaming policy"
" is set but is not supported : %s" % (self.switch,
to_native(not_supported.msg)))
except Exception as generic_exc:
self.module.fail_json(msg="Failed to update vSwitch '%s' due to"
" generic exception : %s" % (self.switch,
to_native(generic_exc)))
self.module.exit_json(**results)
def check_vswitch_configuration(self): def check_vswitch_configuration(self):
hosts = get_all_objs(self.content, [vim.HostSystem]) """
if not hosts: Function to check if vSwitch exists
self.module.fail_json(msg="Unable to find host") Returns: 'present' if vSwitch exists or 'absent' if not
desired_host_system = None
if self.esxi_hostname:
for host_system_obj, host_system_name in iteritems(hosts):
if host_system_name == self.esxi_hostname:
desired_host_system = host_system_obj
if desired_host_system:
self.host_system = desired_host_system
else:
self.host_system = list(hosts.keys())[0]
self.vss = find_vswitch_by_name(self.host_system, self.switch)
"""
self.vss = self.find_vswitch_by_name(self.host_system, self.switch)
if self.vss is None: if self.vss is None:
return 'absent' return 'absent'
else: else:
return 'present' return 'present'
@staticmethod
def find_vswitch_by_name(host, vswitch_name):
"""
Function to find and return vSwitch managed object
Args:
host: Host system managed object
vswitch_name: Name of vSwitch to find
Returns: vSwitch managed object if found, else None
"""
for vss in host.configManager.networkSystem.networkInfo.vswitch:
if vss.name == vswitch_name:
return vss
return None
def main(): def main():
argument_spec = vmware_argument_spec() argument_spec = vmware_argument_spec()
argument_spec.update(dict( argument_spec.update(dict(
switch=dict(type='str', required=True, aliases=['switch_name']), switch=dict(type='str', required=True, aliases=['switch_name']),
nics=dict(type='list', aliases=['nic_name']), nics=dict(type='list', aliases=['nic_name'], default=[]),
number_of_ports=dict(type='int', default=128), number_of_ports=dict(type='int', default=128),
mtu=dict(type='int', default=1500), mtu=dict(type='int', default=1500),
state=dict(type='str', default='present', choices=['absent', 'present'])), state=dict(type='str', default='present', choices=['absent', 'present'])),

@ -2,68 +2,69 @@
# Copyright: (c) 2017, Abhijeet Kasurde <akasurde@redhat.com> # Copyright: (c) 2017, Abhijeet Kasurde <akasurde@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
- name: Make sure pyvmomi is installed # TODO: akasurde: VCSIM does not suport network manager system
pip: #- name: Make sure pyvmomi is installed
name: pyvmomi # pip:
state: latest # name: pyvmomi
when: "{{ ansible_user_id == 'root' }}" # state: latest
# when: "{{ ansible_user_id == 'root' }}"
- name: store the vcenter container ip
set_fact: #- name: store the vcenter container ip
vcsim: '{{ lookup("env", "vcenter_host") }}' # set_fact:
# vcsim: '{{ lookup("env", "vcenter_host") }}'
- debug:
var: vcsim #- debug:
# var: vcsim
- name: Wait for Flask controller to come up online
wait_for: #- name: Wait for Flask controller to come up online
host: '{{ vcsim }}' # wait_for:
port: 5000 # host: '{{ vcsim }}'
state: started # port: 5000
# state: started
- name: Kill vcsim
uri: #- name: Kill vcsim
url: http://{{ vcsim }}:5000/killall # uri:
# url: http://{{ vcsim }}:5000/killall
- name: Start vcsim
uri: #- name: Start vcsim
url: http://{{ vcsim }}:5000/spawn?cluster=2 # uri:
register: vcsim_instance # url: http://{{ vcsim }}:5000/spawn?cluster=2
# register: vcsim_instance
- name: Wait for Flask controller to come up online
wait_for: #- name: Wait for Flask controller to come up online
host: '{{ vcsim }}' # wait_for:
port: 443 # host: '{{ vcsim }}'
state: started # port: 443
# state: started
- debug:
var: vcsim_instance #- debug:
# var: vcsim_instance
# FIXME: Implement check-mode support # FIXME: Implement check-mode support
- name: Add a nic to a switch (check-mode) #- name: Add a nic to a switch (check-mode)
vmware_vswitch: &add_nic # vmware_vswitch: &add_nic
hostname: '{{ vcsim }}' # hostname: '{{ vcsim }}'
username: '{{ vcsim_instance.json.username }}' # username: '{{ vcsim_instance.json.username }}'
password: '{{ vcsim_instance.json.password }}' # password: '{{ vcsim_instance.json.password }}'
validate_certs: no # validate_certs: no
switch: vmswitch_0001 # switch: vmswitch_0001
nics: vnic_1 # nics: vnic_1
state: present # state: present
check_mode: yes # check_mode: yes
register: add_nic_check # register: add_nic_check
- assert: #- assert:
that: # that:
# - add_nic_check.changed == true # - add_nic_check.changed == true
- add_nic_check.skipped == true # - add_nic_check.skipped == true
- name: Add a nic to a switch #- name: Add a nic to a switch
vmware_vswitch: *add_nic # vmware_vswitch: *add_nic
register: add_nic_run # register: add_nic_run
- assert: #- assert:
that: # that:
- add_nic_run.changed == true # - add_nic_run.changed == true
## FIXME: Implement check-mode support ## FIXME: Implement check-mode support
#- name: Add a nic to a switch again (check-mode) #- name: Add a nic to a switch again (check-mode)
@ -127,30 +128,29 @@
# that: # that:
# - remove_nic_again_run.changed == false # - remove_nic_again_run.changed == false
#- name: get a list of Host Systems from vcsim
# uri:
# url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=H' }}"
# register: host_systems
- name: get a list of Host Systems from vcsim #- name: get a host system
uri: # set_fact: hs1="{{ host_systems['json'][0] | basename }}"
url: "{{ 'http://' + vcsim + ':5000/govc_find?filter=H' }}"
register: host_systems
- name: get a host system #- debug: var=hs1
set_fact: hs1="{{ host_systems['json'][0] | basename }}"
- debug: var=hs1 #- name: Add vswitch to a specific host system
# vmware_vswitch:
# validate_certs: False
# hostname: "{{ vcsim }}"
# username: "{{ vcsim_instance['json']['username'] }}"
# password: "{{ vcsim_instance['json']['password'] }}"
# switch: vmswitch_0002
# nics: vnic_1
# esxi_hostname: hs1
# register: add_vswitch_with_host_system
- name: Add vswitch to a specific host system #- debug: var=add_vswitch_with_host_system
vmware_vswitch:
validate_certs: False
hostname: "{{ vcsim }}"
username: "{{ vcsim_instance['json']['username'] }}"
password: "{{ vcsim_instance['json']['password'] }}"
switch: vmswitch_0002
nics: vnic_1
esxi_hostname: hs1
register: add_vswitch_with_host_system
- debug: var=add_vswitch_with_host_system #- assert:
# that:
- assert: # - add_vswitch_with_host_system.changed == true
that:
- add_vswitch_with_host_system.changed == true

Loading…
Cancel
Save