479 lines
20 KiB
Python
479 lines
20 KiB
Python
#!/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_uplink_pg
|
|
short_description: Manage uplink portproup configuration of a Distributed Switch
|
|
description:
|
|
- This module can be used to configure the uplink portgroup of a Distributed Switch.
|
|
version_added: 2.8
|
|
author:
|
|
- Christian Kotte (@ckotte)
|
|
notes:
|
|
- Tested on vSphere 6.5 and 6.7
|
|
requirements:
|
|
- "python >= 2.6"
|
|
- PyVmomi
|
|
options:
|
|
switch:
|
|
description:
|
|
- The name of the Distributed Switch.
|
|
type: str
|
|
required: True
|
|
aliases: ['dvswitch']
|
|
name:
|
|
description:
|
|
- The name of the uplink portgroup.
|
|
- The current name will be used if not specified.
|
|
type: str
|
|
description:
|
|
description:
|
|
- The description of the uplink portgroup.
|
|
type: str
|
|
advanced:
|
|
description:
|
|
- Dictionary which configures the advanced policy settings for the uplink portgroup.
|
|
- 'Valid attributes are:'
|
|
- '- C(port_config_reset_at_disconnect) (bool): indicates if the configuration of a port is reset automatically after disconnect. (default: true)'
|
|
- '- C(block_override) (bool): indicates if the block policy can be changed per port. (default: true)'
|
|
- '- C(netflow_override) (bool): indicates if the NetFlow policy can be changed per port. (default: false)'
|
|
- '- C(traffic_filter_override) (bool): indicates if the traffic filter can be changed per port. (default: false)'
|
|
- '- C(vendor_config_override) (bool): indicates if the vendor config can be changed per port. (default: false)'
|
|
- '- C(vlan_override) (bool): indicates if the vlan can be changed per port. (default: false)'
|
|
required: False
|
|
default: {
|
|
port_config_reset_at_disconnect: True,
|
|
block_override: True,
|
|
vendor_config_override: False,
|
|
vlan_override: False,
|
|
netflow_override: False,
|
|
traffic_filter_override: False,
|
|
}
|
|
aliases: ['port_policy']
|
|
vlan_trunk_range:
|
|
description:
|
|
- The VLAN trunk range that should be configured with the uplink portgroup.
|
|
- 'This can be a combination of multiple ranges and numbers, example: [ 2-3967, 4049-4092 ].'
|
|
type: list
|
|
default: [ '0-4094' ]
|
|
lacp:
|
|
description:
|
|
- Dictionary which configures the LACP settings for the uplink portgroup.
|
|
- The options are only used if the LACP support mode is set to 'basic'.
|
|
- 'The following parameters are required:'
|
|
- '- C(status) (str): Indicates if LACP is enabled. (default: disabled)'
|
|
- '- C(mode) (str): The negotiating state of the uplinks/ports. (default: passive)'
|
|
required: False
|
|
default: {
|
|
status: 'disabled',
|
|
mode: 'passive',
|
|
}
|
|
netflow_enabled:
|
|
description:
|
|
- Indicates if NetFlow is enabled on the uplink portgroup.
|
|
type: bool
|
|
default: False
|
|
block_all_ports:
|
|
description:
|
|
- Indicates if all ports are blocked on the uplink portgroup.
|
|
type: bool
|
|
default: False
|
|
extends_documentation_fragment: vmware.documentation
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Configure Uplink portgroup
|
|
vmware_dvswitch_uplink_pg:
|
|
hostname: '{{ inventory_hostname }}'
|
|
username: '{{ vcsa_username }}'
|
|
password: '{{ vcsa_password }}'
|
|
switch: dvSwitch
|
|
name: dvSwitch-DVUplinks
|
|
advanced:
|
|
port_config_reset_at_disconnect: True
|
|
block_override: True
|
|
vendor_config_override: False
|
|
vlan_override: False
|
|
netflow_override: False
|
|
traffic_filter_override: False
|
|
vlan_trunk_range:
|
|
- '0-4094'
|
|
netflow_enabled: False
|
|
block_all_ports: False
|
|
delegate_to: localhost
|
|
|
|
- name: Enabled LACP on Uplink portgroup
|
|
vmware_dvswitch_uplink_pg:
|
|
hostname: '{{ inventory_hostname }}'
|
|
username: '{{ vcsa_username }}'
|
|
password: '{{ vcsa_password }}'
|
|
switch: dvSwitch
|
|
lacp:
|
|
status: enabled
|
|
mode: active
|
|
delegate_to: localhost
|
|
'''
|
|
|
|
RETURN = """
|
|
result:
|
|
description: information about performed operation
|
|
returned: always
|
|
type: str
|
|
sample: {
|
|
"adv_block_ports": true,
|
|
"adv_netflow": false,
|
|
"adv_reset_at_disconnect": true,
|
|
"adv_traffic_filtering": false,
|
|
"adv_vendor_conf": false,
|
|
"adv_vlan": false,
|
|
"block_all_ports": false,
|
|
"changed": false,
|
|
"description": null,
|
|
"dvswitch": "dvSwitch",
|
|
"lacp_status": "disabled",
|
|
"lacp_status_previous": "enabled",
|
|
"name": "dvSwitch-DVUplinks",
|
|
"netflow_enabled": false,
|
|
"result": "Uplink portgroup already configured properly",
|
|
"vlan_trunk_range": [
|
|
"2-3967",
|
|
"4049-4092"
|
|
]
|
|
}
|
|
"""
|
|
|
|
try:
|
|
from pyVmomi import vim
|
|
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 VMwareDvSwitchUplinkPortgroup(PyVmomi):
|
|
"""Class to manage a uplink portgroup on a Distributed Virtual Switch"""
|
|
|
|
def __init__(self, module):
|
|
super(VMwareDvSwitchUplinkPortgroup, self).__init__(module)
|
|
self.switch_name = self.module.params['switch']
|
|
self.uplink_pg_name = self.params['name']
|
|
self.uplink_pg_description = self.params['description']
|
|
self.uplink_pg_reset = self.params['advanced'].get('port_config_reset_at_disconnect')
|
|
self.uplink_pg_block_ports = self.params['advanced'].get('block_override')
|
|
self.uplink_pg_vendor_conf = self.params['advanced'].get('vendor_config_override')
|
|
self.uplink_pg_vlan = self.params['advanced'].get('vlan_override')
|
|
self.uplink_pg_netflow = self.params['advanced'].get('netflow_override')
|
|
self.uplink_pg_tf = self.params['advanced'].get('traffic_filter_override')
|
|
self.uplink_pg_vlan_trunk_range = self.params['vlan_trunk_range']
|
|
self.uplink_pg_netflow_enabled = self.params['netflow_enabled']
|
|
self.uplink_pg_block_all_ports = self.params['block_all_ports']
|
|
self.lacp_status = self.params['lacp'].get('status')
|
|
self.lacp_mode = self.params['lacp'].get('mode')
|
|
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)
|
|
self.support_mode = self.dvs.config.lacpApiVersion
|
|
|
|
def ensure(self):
|
|
"""Manage uplink portgroup"""
|
|
changed = changed_uplink_pg_policy = changed_vlan_trunk_range = changed_lacp = False
|
|
results = dict(changed=changed)
|
|
results['dvswitch'] = self.switch_name
|
|
changed_list = []
|
|
|
|
uplink_pg_spec = vim.dvs.DistributedVirtualPortgroup.ConfigSpec()
|
|
# Use the same version in the new spec; The version will be increased by one by the API automatically
|
|
uplink_pg_spec.configVersion = self.dvs.config.uplinkPortgroup[0].config.configVersion
|
|
uplink_pg_config = self.dvs.config.uplinkPortgroup[0].config
|
|
|
|
# Check name
|
|
if self.uplink_pg_name:
|
|
results['name'] = self.uplink_pg_name
|
|
if uplink_pg_config.name != self.uplink_pg_name:
|
|
changed = True
|
|
changed_list.append("name")
|
|
results['name_previous'] = uplink_pg_config.name
|
|
uplink_pg_spec.name = self.uplink_pg_name
|
|
else:
|
|
results['name'] = uplink_pg_config.name
|
|
|
|
# Check description
|
|
results['description'] = self.uplink_pg_description
|
|
if uplink_pg_config.description != self.uplink_pg_description:
|
|
changed = True
|
|
changed_list.append("description")
|
|
results['description_previous'] = uplink_pg_config.description
|
|
uplink_pg_spec.description = self.uplink_pg_description
|
|
|
|
# Check port policies
|
|
results['adv_reset_at_disconnect'] = self.uplink_pg_reset
|
|
results['adv_block_ports'] = self.uplink_pg_block_ports
|
|
results['adv_vendor_conf'] = self.uplink_pg_vendor_conf
|
|
results['adv_vlan'] = self.uplink_pg_vlan
|
|
results['adv_netflow'] = self.uplink_pg_netflow
|
|
results['adv_traffic_filtering'] = self.uplink_pg_tf
|
|
uplink_pg_policy_spec = vim.dvs.VmwareDistributedVirtualSwitch.VMwarePortgroupPolicy()
|
|
uplink_pg_policy_spec.portConfigResetAtDisconnect = self.uplink_pg_reset
|
|
uplink_pg_policy_spec.blockOverrideAllowed = self.uplink_pg_block_ports
|
|
uplink_pg_policy_spec.vendorConfigOverrideAllowed = self.uplink_pg_vendor_conf
|
|
uplink_pg_policy_spec.vlanOverrideAllowed = self.uplink_pg_vlan
|
|
uplink_pg_policy_spec.ipfixOverrideAllowed = self.uplink_pg_netflow
|
|
uplink_pg_policy_spec.trafficFilterOverrideAllowed = self.uplink_pg_tf
|
|
# There's no information available if the following option are deprecated, but
|
|
# they aren't visible in the vSphere Client
|
|
uplink_pg_policy_spec.shapingOverrideAllowed = False
|
|
uplink_pg_policy_spec.livePortMovingAllowed = False
|
|
uplink_pg_policy_spec.uplinkTeamingOverrideAllowed = False
|
|
uplink_pg_policy_spec.securityPolicyOverrideAllowed = False
|
|
uplink_pg_policy_spec.networkResourcePoolOverrideAllowed = False
|
|
# Check policies
|
|
if uplink_pg_config.policy.portConfigResetAtDisconnect != self.uplink_pg_reset:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_reset_at_disconnect_previous'] = uplink_pg_config.policy.portConfigResetAtDisconnect
|
|
if uplink_pg_config.policy.blockOverrideAllowed != self.uplink_pg_block_ports:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_block_ports_previous'] = uplink_pg_config.policy.blockOverrideAllowed
|
|
if uplink_pg_config.policy.vendorConfigOverrideAllowed != self.uplink_pg_vendor_conf:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_vendor_conf_previous'] = uplink_pg_config.policy.vendorConfigOverrideAllowed
|
|
if uplink_pg_config.policy.vlanOverrideAllowed != self.uplink_pg_vlan:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_vlan_previous'] = uplink_pg_config.policy.vlanOverrideAllowed
|
|
if uplink_pg_config.policy.ipfixOverrideAllowed != self.uplink_pg_netflow:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_netflow_previous'] = uplink_pg_config.policy.ipfixOverrideAllowed
|
|
if uplink_pg_config.policy.trafficFilterOverrideAllowed != self.uplink_pg_tf:
|
|
changed_uplink_pg_policy = True
|
|
results['adv_traffic_filtering_previous'] = uplink_pg_config.policy.trafficFilterOverrideAllowed
|
|
if changed_uplink_pg_policy:
|
|
changed = True
|
|
changed_list.append("advanced")
|
|
uplink_pg_spec.policy = uplink_pg_policy_spec
|
|
|
|
uplink_pg_spec.defaultPortConfig = vim.dvs.VmwareDistributedVirtualSwitch.VmwarePortConfigPolicy()
|
|
|
|
# Check VLAN trunk
|
|
results['vlan_trunk_range'] = self.uplink_pg_vlan_trunk_range
|
|
vlan_id_ranges = self.uplink_pg_vlan_trunk_range
|
|
trunk_vlan_spec = vim.dvs.VmwareDistributedVirtualSwitch.TrunkVlanSpec()
|
|
vlan_id_list = []
|
|
for vlan_id_range in vlan_id_ranges:
|
|
vlan_id_range_found = False
|
|
vlan_id_start, vlan_id_end = self.get_vlan_ids_from_range(vlan_id_range)
|
|
# Check if range is already configured
|
|
for current_vlan_id_range in uplink_pg_config.defaultPortConfig.vlan.vlanId:
|
|
if current_vlan_id_range.start == int(vlan_id_start) and current_vlan_id_range.end == int(vlan_id_end):
|
|
vlan_id_range_found = True
|
|
break
|
|
if vlan_id_range_found is False:
|
|
changed_vlan_trunk_range = True
|
|
vlan_id_list.append(
|
|
vim.NumericRange(start=int(vlan_id_start), end=int(vlan_id_end))
|
|
)
|
|
# Check if range needs to be removed
|
|
for current_vlan_id_range in uplink_pg_config.defaultPortConfig.vlan.vlanId:
|
|
vlan_id_range_found = False
|
|
for vlan_id_range in vlan_id_ranges:
|
|
vlan_id_start, vlan_id_end = self.get_vlan_ids_from_range(vlan_id_range)
|
|
if (current_vlan_id_range.start == int(vlan_id_start)
|
|
and current_vlan_id_range.end == int(vlan_id_end)):
|
|
vlan_id_range_found = True
|
|
break
|
|
if vlan_id_range_found is False:
|
|
changed_vlan_trunk_range = True
|
|
trunk_vlan_spec.vlanId = vlan_id_list
|
|
if changed_vlan_trunk_range:
|
|
changed = True
|
|
changed_list.append("vlan trunk range")
|
|
current_vlan_id_list = []
|
|
for current_vlan_id_range in uplink_pg_config.defaultPortConfig.vlan.vlanId:
|
|
if current_vlan_id_range.start == current_vlan_id_range.end:
|
|
current_vlan_id_range_string = current_vlan_id_range.start
|
|
else:
|
|
current_vlan_id_range_string = '-'.join(
|
|
[str(current_vlan_id_range.start), str(current_vlan_id_range.end)]
|
|
)
|
|
current_vlan_id_list.append(current_vlan_id_range_string)
|
|
results['vlan_trunk_range_previous'] = current_vlan_id_list
|
|
uplink_pg_spec.defaultPortConfig.vlan = trunk_vlan_spec
|
|
|
|
# Check LACP
|
|
lacp_support_mode = self.get_lacp_support_mode(self.support_mode)
|
|
if lacp_support_mode == 'basic':
|
|
results['lacp_status'] = self.lacp_status
|
|
lacp_spec = vim.dvs.VmwareDistributedVirtualSwitch.UplinkLacpPolicy()
|
|
lacp_enabled = False
|
|
if self.lacp_status == 'enabled':
|
|
lacp_enabled = True
|
|
if uplink_pg_config.defaultPortConfig.lacpPolicy.enable.value != lacp_enabled:
|
|
changed_lacp = True
|
|
changed_list.append("lacp status")
|
|
if uplink_pg_config.defaultPortConfig.lacpPolicy.enable.value:
|
|
results['lacp_status_previous'] = 'enabled'
|
|
else:
|
|
results['lacp_status_previous'] = 'disabled'
|
|
lacp_spec.enable = vim.BoolPolicy()
|
|
lacp_spec.enable.inherited = False
|
|
lacp_spec.enable.value = lacp_enabled
|
|
if lacp_enabled and uplink_pg_config.defaultPortConfig.lacpPolicy.mode.value != self.lacp_mode:
|
|
results['lacp_mode'] = self.lacp_mode
|
|
changed_lacp = True
|
|
changed_list.append("lacp mode")
|
|
results['lacp_mode_previous'] = uplink_pg_config.defaultPortConfig.lacpPolicy.mode.value
|
|
lacp_spec.mode = vim.StringPolicy()
|
|
lacp_spec.mode.inherited = False
|
|
lacp_spec.mode.value = self.lacp_mode
|
|
if changed_lacp:
|
|
changed = True
|
|
uplink_pg_spec.defaultPortConfig.lacpPolicy = lacp_spec
|
|
|
|
# Check NetFlow
|
|
results['netflow_enabled'] = self.uplink_pg_netflow_enabled
|
|
netflow_enabled_spec = vim.BoolPolicy()
|
|
netflow_enabled_spec.inherited = False
|
|
netflow_enabled_spec.value = self.uplink_pg_netflow_enabled
|
|
if uplink_pg_config.defaultPortConfig.ipfixEnabled.value != self.uplink_pg_netflow_enabled:
|
|
changed = True
|
|
results['netflow_enabled_previous'] = uplink_pg_config.defaultPortConfig.ipfixEnabled.value
|
|
changed_list.append("netflow")
|
|
uplink_pg_spec.defaultPortConfig.ipfixEnabled = netflow_enabled_spec
|
|
|
|
# TODO: Check Traffic filtering and marking
|
|
|
|
# Check Block all ports
|
|
results['block_all_ports'] = self.uplink_pg_block_all_ports
|
|
block_all_ports_spec = vim.BoolPolicy()
|
|
block_all_ports_spec.inherited = False
|
|
block_all_ports_spec.value = self.uplink_pg_block_all_ports
|
|
if uplink_pg_config.defaultPortConfig.blocked.value != self.uplink_pg_block_all_ports:
|
|
changed = True
|
|
changed_list.append("block all ports")
|
|
results['block_all_ports_previous'] = uplink_pg_config.defaultPortConfig.blocked.value
|
|
uplink_pg_spec.defaultPortConfig.blocked = block_all_ports_spec
|
|
|
|
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:
|
|
try:
|
|
task = self.dvs.config.uplinkPortgroup[0].ReconfigureDVPortgroup_Task(uplink_pg_spec)
|
|
wait_for_task(task)
|
|
except TaskError as invalid_argument:
|
|
self.module.fail_json(msg="Failed to update uplink portgroup : %s" % to_native(invalid_argument))
|
|
else:
|
|
message = "Uplink portgroup already configured properly"
|
|
results['changed'] = changed
|
|
results['result'] = message
|
|
|
|
self.module.exit_json(**results)
|
|
|
|
@staticmethod
|
|
def get_vlan_ids_from_range(vlan_id_range):
|
|
"""Get start and end VLAN ID from VLAN ID range"""
|
|
try:
|
|
vlan_id_start, vlan_id_end = vlan_id_range.split('-')
|
|
except (AttributeError, TypeError):
|
|
vlan_id_start = vlan_id_end = vlan_id_range
|
|
except ValueError:
|
|
vlan_id_start = vlan_id_end = vlan_id_range.strip()
|
|
return vlan_id_start, vlan_id_end
|
|
|
|
@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 main():
|
|
"""Main"""
|
|
argument_spec = vmware_argument_spec()
|
|
argument_spec.update(
|
|
dict(
|
|
switch=dict(required=True, aliases=['dvswitch']),
|
|
name=dict(type='str'),
|
|
description=dict(type='str'),
|
|
advanced=dict(
|
|
type='dict',
|
|
options=dict(
|
|
port_config_reset_at_disconnect=dict(type='bool', default=True),
|
|
block_override=dict(type='bool', default=True),
|
|
vendor_config_override=dict(type='bool', default=False),
|
|
vlan_override=dict(type='bool', default=False),
|
|
netflow_override=dict(type='bool', default=False),
|
|
traffic_filter_override=dict(type='bool', default=False),
|
|
),
|
|
default=dict(
|
|
port_config_reset_at_disconnect=True,
|
|
block_override=True,
|
|
vendor_config_override=False,
|
|
vlan_override=False,
|
|
netflow_override=False,
|
|
traffic_filter_override=False,
|
|
),
|
|
aliases=['port_policy'],
|
|
),
|
|
lacp=dict(
|
|
type='dict',
|
|
options=dict(
|
|
status=dict(type='str', choices=['enabled', 'disabled'], default=['disabled']),
|
|
mode=dict(type='str', choices=['active', 'passive'], default=['passive']),
|
|
),
|
|
default=dict(
|
|
status='disabled',
|
|
mode='passive',
|
|
),
|
|
),
|
|
vlan_trunk_range=dict(type='list', default=['0-4094']),
|
|
netflow_enabled=dict(type='bool', default=False),
|
|
block_all_ports=dict(type='bool', default=False),
|
|
)
|
|
)
|
|
|
|
module = AnsibleModule(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
)
|
|
|
|
vmware_dvswitch_uplink_pg = VMwareDvSwitchUplinkPortgroup(module)
|
|
vmware_dvswitch_uplink_pg.ensure()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|