VMware: New module vmware_dvswitch_lacp (#48555)

pull/48976/head
Christian Kotte 6 years ago committed by Abhijeet Kasurde
parent fb7b6f9521
commit 3f6878ac84

@ -0,0 +1,403 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2018, Christian Kotte <christian.kotte@gmx.de>
#
# 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_dvswitch_lacp
short_description: Manage LACP configuration on a Distributed Switch
description:
- This module can be used to configure Link Aggregation Control Protocol (LACP) support mode and Link Aggregation Groups (LAGs).
version_added: 2.8
author:
- Christian Kotte (@ckotte)
notes:
- Tested on vSphere 6.7
- You need to run the task two times if you want to remove all LAGs and change the support mode to 'basic'
requirements:
- "python >= 2.6"
- PyVmomi
options:
switch:
description:
- The name of the Distributed Switch to manage.
required: True
aliases: ['dvswitch']
support_mode:
description:
- The LACP support mode.
- 'C(basic): One Link Aggregation Control Protocol group in the switch (singleLag).'
- 'C(enhanced): Multiple Link Aggregation Control Protocol groups in the switch (multipleLag).'
type: str
default: 'basic'
choices: ['basic', 'enhanced']
link_aggregation_groups:
description:
- Can only be used if C(lacp_support) is set to C(enhanced).
- 'The following parameters are required:'
- '- C(name) (string): Name of the LAG.'
- '- C(uplink_number) (int): Number of uplinks. Can 1 to 30.'
- '- C(mode) (string): The negotiating state of the uplinks/ports.'
- ' - choices: [ active, passive ]'
- '- C(load_balancing_mode) (string): Load balancing algorithm.'
- ' - Valid attributes are:'
- ' - srcTcpUdpPort: Source TCP/UDP port number.'
- ' - srcDestIpTcpUdpPortVlan: Source and destination IP, source and destination TCP/UDP port number and VLAN.'
- ' - srcIpVlan: Source IP and VLAN.'
- ' - srcDestTcpUdpPort: Source and destination TCP/UDP port number.'
- ' - srcMac: Source MAC address.'
- ' - destIp: Destination IP.'
- ' - destMac: Destination MAC address.'
- ' - vlan: VLAN only.'
- ' - srcDestIp: Source and Destination IP.'
- ' - srcIpTcpUdpPortVlan: Source IP, TCP/UDP port number and VLAN.'
- ' - srcDestIpTcpUdpPort: Source and destination IP and TCP/UDP port number.'
- ' - srcDestMac: Source and destination MAC address.'
- ' - destIpTcpUdpPort: Destination IP and TCP/UDP port number.'
- ' - srcPortId: Source Virtual Port Id.'
- ' - srcIp: Source IP.'
- ' - srcIpTcpUdpPort: Source IP and TCP/UDP port number.'
- ' - destIpTcpUdpPortVlan: Destination IP, TCP/UDP port number and VLAN.'
- ' - destTcpUdpPort: Destination TCP/UDP port number.'
- ' - destIpVlan: Destination IP and VLAN.'
- ' - srcDestIpVlan: Source and destination IP and VLAN.'
- ' - The default load balancing mode in the vSphere Client is srcDestIpTcpUdpPortVlan.'
- Please see examples for more information.
type: list
extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
- name: Enable enhanced mode on a Distributed Switch
vmware_dvswitch_lacp:
hostname: '{{ inventory_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
switch: dvSwitch
support_mode: enhanced
validate_certs: "{{ validate_vcenter_certs }}"
delegate_to: localhost
loop_control:
label: "{{ item.name }}"
with_items: "{{ vcenter_distributed_switches }}"
- name: Enable enhanced mode and create two LAGs on a Distributed Switch
vmware_dvswitch_lacp:
hostname: '{{ inventory_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
switch: dvSwitch
support_mode: enhanced
link_aggregation_groups:
- name: lag1
uplink_number: 2
mode: active
load_balancing_mode: srcDestIpTcpUdpPortVlan
- name: lag2
uplink_number: 2
mode: passive
load_balancing_mode: srcDestIp
validate_certs: "{{ validate_vcenter_certs }}"
delegate_to: localhost
loop_control:
label: "{{ item.name }}"
with_items: "{{ vcenter_distributed_switches }}"
'''
RETURN = """
result:
description: information about performed operation
returned: always
type: string
sample: {
"changed": true,
"dvswitch": "dvSwitch",
"link_aggregation_groups": [
{"load_balancing_mode": "srcDestIpTcpUdpPortVlan", "mode": "active", "name": "lag1", "uplink_number": 2},
{"load_balancing_mode": "srcDestIp", "mode": "active", "name": "lag2", "uplink_number": 2}
],
"link_aggregation_groups_previous": [],
"support_mode": "enhanced",
"result": "lacp lags changed"
}
"""
try:
from pyVmomi import vim, vmodl
except ImportError:
pass
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.vmware import (
PyVmomi, TaskError, find_dvs_by_name, vmware_argument_spec, wait_for_task
)
class VMwareDvSwitchLacp(PyVmomi):
"""Class to manage a LACP on a Distributed Virtual Switch"""
def __init__(self, module):
super(VMwareDvSwitchLacp, self).__init__(module)
self.switch_name = self.module.params['switch']
self.support_mode = self.module.params['support_mode']
self.link_aggregation_groups = self.module.params['link_aggregation_groups']
if self.support_mode == 'basic' and (
self.link_aggregation_groups and not (
len(self.link_aggregation_groups) == 1 and self.link_aggregation_groups[0] == '')):
self.module.fail_json(
msg="LAGs can only be configured if 'support_mode' is set to 'enhanced'!"
)
self.dvs = find_dvs_by_name(self.content, self.switch_name)
if self.dvs is None:
self.module.fail_json(msg="Failed to find DVS %s" % self.switch_name)
def ensure(self):
"""Manage LACP configuration"""
changed = changed_support_mode = changed_lags = False
results = dict(changed=changed)
results['dvswitch'] = self.switch_name
changed_list = []
spec = vim.dvs.VmwareDistributedVirtualSwitch.ConfigSpec()
spec.configVersion = self.dvs.config.configVersion
# Check support mode
results['support_mode'] = self.support_mode
lacp_support_mode = self.get_lacp_support_mode(self.support_mode)
if self.dvs.config.lacpApiVersion != lacp_support_mode:
changed = changed_support_mode = True
changed_list.append("support mode")
results['support_mode_previous'] = self.get_lacp_support_mode(self.dvs.config.lacpApiVersion)
spec.lacpApiVersion = lacp_support_mode
# Check LAGs
results['link_aggregation_groups'] = self.link_aggregation_groups
if self.link_aggregation_groups and not (
len(self.link_aggregation_groups) == 1 and self.link_aggregation_groups[0] == ''):
if self.dvs.config.lacpGroupConfig:
lacp_lag_list = []
# Check if desired LAGs are configured
for lag in self.link_aggregation_groups:
lag_name, lag_mode, lag_uplink_number, lag_load_balancing_mode = self.get_lacp_lag_options(lag)
lag_found = False
for lacp_group in self.dvs.config.lacpGroupConfig:
if lacp_group.name == lag_name:
lag_found = True
if (lag_mode != lacp_group.mode or
lag_uplink_number != lacp_group.uplinkNum or
lag_load_balancing_mode != lacp_group.loadbalanceAlgorithm):
changed = changed_lags = True
lacp_lag_list.append(
self.create_lacp_group_spec(
'edit',
lacp_group.key, lag_name, lag_uplink_number, lag_mode, lag_load_balancing_mode
)
)
break
if lag_found is False:
changed = changed_lags = True
lacp_lag_list.append(
self.create_lacp_group_spec(
'add', None, lag_name, lag_uplink_number, lag_mode, lag_load_balancing_mode
)
)
# Check if LAGs need to be removed
for lacp_group in self.dvs.config.lacpGroupConfig:
lag_found = False
for lag in self.link_aggregation_groups:
result = self.get_lacp_lag_options(lag)
if lacp_group.name == result[0]:
lag_found = True
break
if lag_found is False:
changed = changed_lags = True
lacp_lag_list.append(
self.create_lacp_group_spec('remove', lacp_group.key, lacp_group.name, None, None, None)
)
else:
changed = changed_lags = True
lacp_lag_list = []
for lag in self.link_aggregation_groups:
lag_name, lag_mode, lag_uplink_number, lag_load_balancing_mode = self.get_lacp_lag_options(lag)
lacp_lag_list.append(
self.create_lacp_group_spec(
'add', None, lag_name, lag_uplink_number, lag_mode, lag_load_balancing_mode
)
)
else:
if self.dvs.config.lacpGroupConfig:
changed = changed_lags = True
lacp_lag_list = []
for lacp_group in self.dvs.config.lacpGroupConfig:
lacp_lag_list.append(
self.create_lacp_group_spec('remove', lacp_group.key, lacp_group.name, None, None, None)
)
if changed_lags:
changed_list.append("link aggregation groups")
current_lags_list = []
for lacp_group in self.dvs.config.lacpGroupConfig:
temp_lag = dict()
temp_lag['name'] = lacp_group.name
temp_lag['uplink_number'] = lacp_group.uplinkNum
temp_lag['mode'] = lacp_group.mode
temp_lag['load_balancing_mode'] = lacp_group.loadbalanceAlgorithm
current_lags_list.append(temp_lag)
results['link_aggregation_groups_previous'] = current_lags_list
if changed:
if self.module.check_mode:
changed_suffix = ' would be changed'
else:
changed_suffix = ' changed'
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 += changed_suffix
if not self.module.check_mode:
if changed_support_mode and self.support_mode == 'basic' and changed_lags:
self.update_lacp_group_config(self.dvs, lacp_lag_list)
# NOTE: You need to run the task again to change the support mode to 'basic' as well
# No matter how long you sleep, you will always get the following error in vCenter:
# 'Cannot complete operation due to concurrent modification by another operation.'
# self.update_dvs_config(self.dvs, spec)
else:
if changed_support_mode:
self.update_dvs_config(self.dvs, spec)
if changed_lags:
self.update_lacp_group_config(self.dvs, lacp_lag_list)
else:
message = "LACP already configured properly"
results['changed'] = changed
results['result'] = message
self.module.exit_json(**results)
@staticmethod
def get_lacp_support_mode(mode):
"""Get LACP support mode"""
return_mode = None
if mode == 'basic':
return_mode = 'singleLag'
elif mode == 'enhanced':
return_mode = 'multipleLag'
elif mode == 'singleLag':
return_mode = 'basic'
elif mode == 'multipleLag':
return_mode = 'enhanced'
return return_mode
def get_lacp_lag_options(self, lag):
"""Get and check LACP LAG options"""
lag_name = lag.get('name', None)
if lag_name is None:
self.module.fail_json(msg="Please specify name in lag options as it's a required parameter")
lag_mode = lag.get('mode', None)
if lag_mode is None:
self.module.fail_json(msg="Please specify mode in lag options as it's a required parameter")
lag_uplink_number = lag.get('uplink_number', None)
if lag_uplink_number is None:
self.module.fail_json(msg="Please specify uplink_number in lag options as it's a required parameter")
elif lag_uplink_number > 30:
self.module.fail_json(msg="More than 30 uplinks are not supported in a single LAG!")
lag_load_balancing_mode = lag.get('load_balancing_mode', None)
supported_lb_modes = ['srcTcpUdpPort', 'srcDestIpTcpUdpPortVlan', 'srcIpVlan', 'srcDestTcpUdpPort',
'srcMac', 'destIp', 'destMac', 'vlan', 'srcDestIp', 'srcIpTcpUdpPortVlan',
'srcDestIpTcpUdpPort', 'srcDestMac', 'destIpTcpUdpPort', 'srcPortId', 'srcIp',
'srcIpTcpUdpPort', 'destIpTcpUdpPortVlan', 'destTcpUdpPort', 'destIpVlan', 'srcDestIpVlan']
if lag_load_balancing_mode is None:
self.module.fail_json(msg="Please specify load_balancing_mode in lag options as it's a required parameter")
elif lag_load_balancing_mode not in supported_lb_modes:
self.module.fail_json(msg="The specified load balancing mode '%s' isn't supported!" % lag_load_balancing_mode)
return lag_name, lag_mode, lag_uplink_number, lag_load_balancing_mode
@staticmethod
def create_lacp_group_spec(operation, key, name, uplink_number, mode, load_balancing_mode):
"""
Create LACP group spec
operation: add, edit, or remove
Returns: LACP group spec
"""
lacp_spec = vim.dvs.VmwareDistributedVirtualSwitch.LacpGroupSpec()
lacp_spec.operation = operation
lacp_spec.lacpGroupConfig = vim.dvs.VmwareDistributedVirtualSwitch.LacpGroupConfig()
lacp_spec.lacpGroupConfig.name = name
if operation in ('edit', 'remove'):
lacp_spec.lacpGroupConfig.key = key
if not operation == 'remove':
lacp_spec.lacpGroupConfig.uplinkNum = uplink_number
lacp_spec.lacpGroupConfig.mode = mode
lacp_spec.lacpGroupConfig.loadbalanceAlgorithm = load_balancing_mode
# greyed out in vSphere Client!?
# lacp_spec.vlan = vim.dvs.VmwareDistributedVirtualSwitch.LagVlanConfig()
# lacp_spec.vlan.vlanId = [vim.NumericRange(...)]
# lacp_spec.ipfix = vim.dvs.VmwareDistributedVirtualSwitch.LagIpfixConfig()
# lacp_spec.ipfix.ipfixEnabled = True/False
return lacp_spec
def update_dvs_config(self, switch_object, spec):
"""Update DVS config"""
try:
task = switch_object.ReconfigureDvs_Task(spec)
result = wait_for_task(task)
except TaskError as invalid_argument:
self.module.fail_json(
msg="Failed to update DVS : %s" % to_native(invalid_argument)
)
return result
def update_lacp_group_config(self, switch_object, lacp_group_spec):
"""Update LACP group config"""
try:
task = switch_object.UpdateDVSLacpGroupConfig_Task(lacpGroupSpec=lacp_group_spec)
result = wait_for_task(task)
except vim.fault.DvsFault as dvs_fault:
self.module.fail_json(msg="Update failed due to DVS fault : %s" % to_native(dvs_fault))
except vmodl.fault.NotSupported as not_supported:
self.module.fail_json(
msg="Multiple Link Aggregation Control Protocol groups not supported on the switch : %s" %
to_native(not_supported)
)
except TaskError as invalid_argument:
self.module.fail_json(
msg="Failed to update Link Aggregation Group : %s" % to_native(invalid_argument)
)
return result
def main():
"""Main"""
argument_spec = vmware_argument_spec()
argument_spec.update(
dict(
switch=dict(required=True, aliases=['dvswitch']),
support_mode=dict(default='basic', choices=['basic', 'enhanced']),
link_aggregation_groups=dict(default=[], type='list'),
)
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
vmware_dvswitch_lacp = VMwareDvSwitchLacp(module)
vmware_dvswitch_lacp.ensure()
if __name__ == '__main__':
main()
Loading…
Cancel
Save