diff --git a/lib/ansible/modules/network/f5/bigip_virtual_server.py b/lib/ansible/modules/network/f5/bigip_virtual_server.py index dec6eb3f11..82097bd5c2 100644 --- a/lib/ansible/modules/network/f5/bigip_virtual_server.py +++ b/lib/ansible/modules/network/f5/bigip_virtual_server.py @@ -279,6 +279,34 @@ options: - When creating a new virtual server, the default is C(enabled). type: bool version_added: 2.6 + source_port: + description: + - Specifies whether the system preserves the source port of the connection. + - When creating a new virtual server, if this parameter is not specified, the default is C(preserve). + choices: + - preserve + - preserve-strict + - change + version_added: 2.8 + mirror: + description: + - Specifies that the system mirrors connections on each member of a redundant pair. + - When creating a new virtual server, if this parameter is not specified, the default is C(disabled). + type: bool + version_added: 2.8 + mask: + description: + - Specifies the destination address network mask. This parameter will work with IPv4 and IPv6 tye of addresses. + - This is an optional parameter which can be specified when creating or updating virtual server. + - If C(destination) is provided in CIDR notation format and C(mask) is provided the mask parameter takes + precedence. + - If catchall destination is specified, i.e. C(0.0.0.0) for IPv4 C(::) for IPv6, + mask parameter is set to C(any) or C(any6) respectively) + - When the C(destination) is provided not in CIDR notation and C(mask) is not specified, C(255.255.255.255) or + C(ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) is set for IPv4 and IPv6 addresses respectively. + - When C(destination) is provided in CIDR notation format and C(mask) is not specified the mask parameter is + inferred from C(destination). + version_added: 2.8 ip_protocol: description: - Specifies a network protocol name you want the system to use to direct traffic @@ -364,6 +392,7 @@ options: extends_documentation_fragment: f5 author: - Tim Rupp (@caphrim007) + - Wojciech Wypior (@wojtek0806) ''' EXAMPLES = r''' @@ -608,6 +637,16 @@ port_translation: returned: changed type: bool sample: True +source_port: + description: Specifies whether the system preserves the source port of the connection. + returned: changed + type: string + sample: change +mirror: + description: Specifies that the system mirrors connections on each member of a redundant pair. + returned: changed + type: bool + sample: True ip_protocol: description: The new value of the IP protocol. returned: changed @@ -629,7 +668,6 @@ security_log_profiles: type: list sample: ['/Common/profile1', '/Common/profile2'] ''' - import os import re @@ -652,10 +690,13 @@ try: from library.module_utils.network.f5.common import transform_name from library.module_utils.network.f5.common import mark_managed_by from library.module_utils.network.f5.common import only_has_managed_metadata + from library.module_utils.network.f5.common import flatten_boolean from library.module_utils.network.f5.compare import cmp_simple_list from library.module_utils.network.f5.ipaddress import is_valid_ip from library.module_utils.network.f5.ipaddress import ip_interface from library.module_utils.network.f5.ipaddress import validate_ip_v6_address + from library.module_utils.network.f5.ipaddress import get_netmask + from library.module_utils.network.f5.ipaddress import compress_address except ImportError: from ansible.module_utils.network.f5.bigip import F5RestClient from ansible.module_utils.network.f5.common import MANAGED_BY_ANNOTATION_VERSION @@ -670,10 +711,13 @@ except ImportError: from ansible.module_utils.network.f5.common import transform_name from ansible.module_utils.network.f5.common import mark_managed_by from ansible.module_utils.network.f5.common import only_has_managed_metadata + from ansible.module_utils.network.f5.common import flatten_boolean from ansible.module_utils.network.f5.compare import cmp_simple_list from ansible.module_utils.network.f5.ipaddress import is_valid_ip from ansible.module_utils.network.f5.ipaddress import ip_interface from ansible.module_utils.network.f5.ipaddress import validate_ip_v6_address + from ansible.module_utils.network.f5.ipaddress import get_netmask + from ansible.module_utils.network.f5.ipaddress import compress_address class Parameters(AnsibleF5Parameters): @@ -693,6 +737,7 @@ class Parameters(AnsibleF5Parameters): 'fwStagedPolicy': 'firewall_staged_policy', 'securityLogProfiles': 'security_log_profiles', 'securityNatPolicy': 'security_nat_policy', + 'sourcePort': 'source_port', } api_attributes = [ @@ -725,6 +770,9 @@ class Parameters(AnsibleF5Parameters): 'fwStagedPolicy', 'securityLogProfiles', 'securityNatPolicy', + 'sourcePort', + 'mirror', + 'mask', ] updatables = [ @@ -751,6 +799,9 @@ class Parameters(AnsibleF5Parameters): 'firewall_staged_policy', 'security_log_profiles', 'security_nat_policy', + 'source_port', + 'mirror', + 'mask', ] returnables = [ @@ -781,6 +832,9 @@ class Parameters(AnsibleF5Parameters): 'firewall_staged_policy', 'security_log_profiles', 'security_nat_policy', + 'source_port', + 'mirror', + 'mask', ] profiles_mutex = [ @@ -1072,11 +1126,11 @@ class ApiParameters(Parameters): @property def destination_tuple(self): - Destination = namedtuple('Destination', ['ip', 'port', 'route_domain']) + Destination = namedtuple('Destination', ['ip', 'port', 'route_domain', 'mask']) # Remove the partition if self._values['destination'] is None: - result = Destination(ip=None, port=None, route_domain=None) + result = Destination(ip=None, port=None, route_domain=None, mask=None) return result destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination']) @@ -1084,7 +1138,8 @@ class ApiParameters(Parameters): result = Destination( ip=destination, port=None, - route_domain=None + route_domain=None, + mask=self.mask ) return result @@ -1114,7 +1169,8 @@ class ApiParameters(Parameters): result = Destination( ip=matches.group('ip'), port=port, - route_domain=int(matches.group('route_domain')) + route_domain=int(matches.group('route_domain')), + mask=self.mask ) return result @@ -1129,7 +1185,8 @@ class ApiParameters(Parameters): result = Destination( ip=matches.group('ip'), port=None, - route_domain=int(matches.group('route_domain')) + route_domain=int(matches.group('route_domain')), + mask=self.mask ) return result @@ -1144,7 +1201,8 @@ class ApiParameters(Parameters): result = Destination( ip=ip, port=int(port), - route_domain=None + route_domain=None, + mask=self.mask ) return result elif len(parts) == 2: @@ -1163,11 +1221,12 @@ class ApiParameters(Parameters): result = Destination( ip=ip, port=port, - route_domain=None + route_domain=None, + mask=self.mask ) return result else: - result = Destination(ip=None, port=None, route_domain=None) + result = Destination(ip=None, port=None, route_domain=None, mask=None) return result @property @@ -1381,7 +1440,7 @@ class ModuleParameters(Parameters): @property def destination(self): - addr = self._values['destination'].split("%")[0] + addr = self._values['destination'].split("%")[0].split('/')[0] if not is_valid_ip(addr): raise F5ModuleError( "The provided destination is not a valid IP address" @@ -1390,15 +1449,38 @@ class ModuleParameters(Parameters): return result @property - def destination_tuple(self): - Destination = namedtuple('Destination', ['ip', 'port', 'route_domain']) + def route_domain(self): if self._values['destination'] is None: - result = Destination(ip=None, port=None, route_domain=None) + return None + result = self._values['destination'].split("%") + if len(result) > 1: + return int(result[1]) + return None + + @property + def destination_tuple(self): + Destination = namedtuple('Destination', ['ip', 'port', 'route_domain', 'mask']) + if self._values['destination'] is None: + result = Destination(ip=None, port=None, route_domain=None, mask=None) return result - addr = self._values['destination'].split("%")[0] - result = Destination(ip=addr, port=self.port, route_domain=self.route_domain) + sanitized = self._values['destination'].split("%")[0].split('/')[0] + addr = compress_address(u'{0}'.format(sanitized)) + result = Destination(ip=addr, port=self.port, route_domain=self.route_domain, mask=self.mask) return result + @property + def mask(self): + if self._values['destination'] is None: + return None + addr = self._values['destination'].split("%")[0] + if addr in ['0.0.0.0', '0.0.0.0/any', '0.0.0.0/0']: + return 'any' + if addr in ['::', '::/0', '::/any6']: + return 'any6' + if self._values['mask'] is None: + return get_netmask(u'{0}'.format(addr)) + return compress_address(u'{0}'.format(self._values['mask'])) + @property def port(self): if self._values['port'] is None: @@ -1705,6 +1787,15 @@ class ModuleParameters(Parameters): return result return None + @property + def mirror(self): + result = flatten_boolean(self._values['mirror']) + if result is None: + return None + if result == 'yes': + return 'enabled' + return 'disabled' + class Changes(Parameters): pass @@ -1828,6 +1919,14 @@ class UsableChanges(Changes): class ReportableChanges(Changes): + @property + def mirror(self): + if self._values['mirror'] is None: + return None + elif self._values['mirror'] == 'enabled': + return 'yes' + return 'no' + @property def snat(self): if self._values['snat'] is None: @@ -2860,7 +2959,7 @@ class ModuleManager(object): return True return False - def exists(self): # lgtm [py/similar-function] + def exists(self): uri = "https://{0}:{1}/mgmt/tm/ltm/virtual/{2}".format( self.client.provider['server'], self.client.provider['server_port'], @@ -3017,6 +3116,11 @@ class ArgumentSpec(object): ), address_translation=dict(type='bool'), port_translation=dict(type='bool'), + source_port=dict( + choices=[ + 'preserve', 'preserve-strict', 'change' + ] + ), ip_protocol=dict( choices=[ 'ah', 'any', 'bna', 'esp', 'etherip', 'gre', 'icmp', 'ipencap', 'ipv6', @@ -3031,6 +3135,8 @@ class ArgumentSpec(object): 'performance-http', 'performance-l4', 'reject', 'stateless', 'dhcp' ] ), + mirror=dict(type='bool'), + mask=dict(), firewall_staged_policy=dict(), firewall_enforced_policy=dict(), security_log_profiles=dict(type='list'), @@ -3060,8 +3166,9 @@ def main(): mutually_exclusive=spec.mutually_exclusive ) + client = F5RestClient(**module.params) + try: - client = F5RestClient(**module.params) mm = ModuleManager(module=module, client=client) results = mm.exec_module() exit_json(module, results, client)